diff --git a/.agents/skills/benchmark-report/SKILL.md b/.agents/skills/benchmark-report/SKILL.md index 596f0552d..9de3f1751 100644 --- a/.agents/skills/benchmark-report/SKILL.md +++ b/.agents/skills/benchmark-report/SKILL.md @@ -12,11 +12,20 @@ Read the repository's `AGENTS.md` first. Project rules override this general ski Before writing report HTML, resolve the track through `scripts/report_template.py`. Inspect the maintained implementation and its pinned example. Reuse that implementation and its release checks; a different cohort or diagnostic evidence state calls for a data adapter or explicit mode. If no track fits, create the smallest reusable renderer with a synthetic runnable example and behavioral checks, then register it in `configs/report-templates.json`. Improve the owning template and this skill from demonstrated gaps, without embedding run-specific prose or copying old measurements. A report must carry the selected template identity and input hashes so its release check can detect a bypass. Derive bilingual counts, budgets and method labels from the selected inputs. +Bind cohort-specific access and interface documentation in the participant registry; +reuse engine-level adapter contracts instead of adding per-version renderer tables. Keep report-specific prose and its translations in explicitly bound content; do not choose a historical article from provider names or task-family guesses. Without a content binding, render the maintained data-derived explanation. Prove reuse through the public build and check entries with a different external task/method inventory, not only by searching for old numbers in source code. +Exercise assembly and packaging with that external profile as well. Participant +IDs, labels, interfaces and comparison roles come from the instance registry; +a new cohort must not inherit a historical method's account or timing claims. +Disclose declared literal, fact, body-region, record, resource and identity checks +separately. A task instruction is not a full-text reference. Preserve frozen +verdicts while exposing passes without verified page identity, blocked-page +markers and unexpected resource URLs as diagnostics. An explicitly requested diagnostic report may be delivered separately from a certified benchmark release. Use the diagnostic build/check entry, label its evidence state and certification gaps on every page, preserve saved scores and outputs, and do not publish rankings or upgrade certification. It does not waive any certified-release reference or review gate. Presentation fixes add no provider calls. Update only the owning code, relevant instructions and demonstrated regression scenarios; do not mechanically modify every skill or copy report-specific conclusions into instructions. diff --git a/.agents/skills/benchmark-report/references/audit-and-revision.md b/.agents/skills/benchmark-report/references/audit-and-revision.md index e11928961..6293a85d2 100644 --- a/.agents/skills/benchmark-report/references/audit-and-revision.md +++ b/.agents/skills/benchmark-report/references/audit-and-revision.md @@ -98,3 +98,14 @@ message. Reproject retained full responses without new product calls when only this selection changed; missing historical envelopes require fresh calls across the full declared correction scope before claiming revised benchmark scores. Reruns of unchanged compiled acceptance-contract tasks freeze the existing executable dataset. They do not require recovering historical qualification raw files or repeating admission review. Source qualification is part of task authoring and reference/rule correction, not a prerequisite for every provider-version comparison. Preserve historical receipts without applying their evidence-file requirements to a new execution protocol. + +A published-snapshot score audit uses the maintained Web Fetch profile adapter, +not a copied renderer. Bind the complete original export, full historical matrix, +initial and corrected scores, per-input original/public/scored hashes, and every +changed decision. Preserve collection facts and timing. Evaluate a suspected +privacy effect with the historical scorer on the same public bytes; a redaction +receipt by itself does not establish causation. Keep source-backed annotation +corrections and executable regression provenance together. Reuse the native +report's routes, interpretation schema, structured history and packer, and run its +explicit snapshot audit browser gate before packaging. The catalog provides the +runnable example and commands. Store instance evidence and prose outside Git. diff --git a/.agents/skills/benchmark-report/references/report-and-release.md b/.agents/skills/benchmark-report/references/report-and-release.md index 3caed87d1..f0997bc78 100644 --- a/.agents/skills/benchmark-report/references/report-and-release.md +++ b/.agents/skills/benchmark-report/references/report-and-release.md @@ -2,9 +2,9 @@ ## Template discovery and diagnostic models -Run `make report-template TRACK=webfetch` (or `trajectory-replay` / `browser-use`) before authoring. The registry returns the maintained owner, current profile, version-pinned example, build and check commands. A reference report supplies presentation conventions, never evidence or the new publication target. Extend the owner instead of copying its HTML into a temporary renderer. +Run `make report-template TRACK=webfetch` (or `trajectory-replay` / `browser-use`) before authoring. The registry returns the maintained owner, version-pinned example, build and check commands. A reference report supplies presentation conventions, never evidence or the new publication target. Extend the owner instead of copying its HTML into a temporary renderer. -For explicitly requested live WebFetch diagnostics, use `make webfetch-diagnostic-report DIAGNOSTIC_PROFILE=evaluation//current-report.json OUTPUT=/absolute/new-output`; `EVIDENCE=/absolute/source` optionally supplies an explicit input. The `webfetch_cohort_diagnostic/1` model contains cohorts, per-case Boolean first-call verdicts, declared summary counts, cause/source labels, review counts and hash-bound reference/output/source/log files. `tests/test_report_templates.py:diagnostic_example` is a runnable two-scenario fixture through the public builder; its test command is registered alongside the variant. The release checker recomputes totals and validates rendered verdicts, complete evidence bindings, dialogs and links. A diagnostic remains diagnostic; this path cannot substitute for a certified build. +For explicitly requested live WebFetch diagnostics, use `make webfetch-diagnostic-report DIAGNOSTIC_PROFILE=/absolute/instance-profile.json OUTPUT=/absolute/new-output`; `EVIDENCE=/absolute/source` optionally supplies an explicit input. The `webfetch_cohort_diagnostic/1` model contains cohorts, per-case Boolean first-call verdicts, declared summary counts, cause/source labels, review counts and hash-bound reference/output/source/log files. `tests/test_report_templates.py:diagnostic_example` is a runnable two-scenario fixture through the public builder; its test command is registered alongside the variant. The release checker recomputes totals and validates rendered verdicts, complete evidence bindings, dialogs and links. A diagnostic remains diagnostic; this path cannot substitute for a certified build. For a new report type, first persist a reusable example and scenario-based positive/negative tests, register its owner and release gate, then generate the requested report. Later corrections update the owner and relevant rules when supported by evidence; do not clone a template per run or embed report-specific conclusions in instructions. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9df22ead..4aeb153d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,8 +45,9 @@ jobs: - run: make setup-test - run: make ci - name: Exercise rendered reference capture + timeout-minutes: 5 run: | uv sync --frozen --extra test --extra headless-eval - uv run --frozen --no-sync playwright install --with-deps chromium + uv run --frozen --no-sync playwright install chromium export BROWSER_EVAL_TEST_BROWSER_BINARY="$(uv run --frozen --no-sync python -c 'from playwright.sync_api import sync_playwright; p = sync_playwright().start(); print(p.chromium.executable_path); p.stop()')" uv run --frozen --no-sync python -m pytest tests/test_browser_observation_contracts.py -q diff --git a/AGENTS.md b/AGENTS.md index 758ae9673..23ce014a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ Browser Eval owns execution adapters, evidence handling and report tooling. `lex # Report maintenance -Repository documentation and contribution guidance use English. Keep the root README and `docs/README.zh-CN.md` aligned; additional README translations are welcome, but other guides need no parallel translation. Preserve original-language source quotations and hash-bound acceptance/evidence artifacts. Report-generation languages follow the track contracts below. +Repository documentation, identifiers, comments and docstrings use English. Report UI messages belong in language catalogs; source quotations and acceptance fixtures retain their original language. Keep the root README and `docs/README.zh-CN.md` aligned; additional README translations are welcome, but other guides need no parallel translation. Preserve original-language source quotations and hash-bound acceptance/evidence artifacts. Report-generation languages follow the track contracts below. - Before report authoring, run `uv run --frozen python scripts/report_template.py ` and inspect its renderer, version-pinned example and checks. `configs/report-templates.json` owns discovery; examples supply presentation conventions, never measurements or publication targets. Extend the selected owner for new cohorts and evidence states. A new track must first gain a runnable synthetic example, evidence/detail behavior tests and a release check, then register those resources. Update the owning code and relevant skill when a repeated gap is demonstrated; keep report-specific findings outside instructions. Do not generate a disposable parallel renderer under a work directory. @@ -12,7 +12,7 @@ Repository documentation and contribution guidance use English. Keep the root RE - Extend the current owner before adding a parallel implementation. A second browser, cohort, dataset size, provider, language or release must select behavior through data, a registry or parameters in the existing pipeline. It must not create a browser-specific runner, copied renderer, numbered implementation or compatibility alias. - Keep command-line files as thin entry points. Parsing, validation, execution, aggregation and publication contracts belong in the track's shared `harness/` module so every entry point uses the same rules. -- Git owns reusable code, compact dataset definitions, acceptance contracts, provenance annotations and path/hash publication bindings. Measured attempts, generated reports, report prose and illustrations, review payloads, diagnostics, temporary queues and run-local manifests live under the configured data home or the pinned Artifact Site release. +- Git owns reusable code, compact dataset definitions, acceptance contracts and provenance annotations. Report-instance profiles and publication bindings belong alongside external evidence, selected explicitly by the caller. Measured attempts, generated reports, report prose and illustrations, review payloads, diagnostics, temporary queues and run-local manifests live under the configured data home or the pinned Artifact Site release. - Keep one supported current implementation of each stage. Remove superseded generators, repair scripts, migrations and their exclusive tests in the same change once no supported entry point calls them. Retain a historical revision in Git only when a current versioned authority names it and reproduction requires its bytes. - Treat a large diff as a design signal. Before adding files, identify the existing owner, the smallest reusable extension and the data that can stay outside Git. File or line counts alone are not release gates; unexplained duplicate stages, private evidence and unreferenced historical versions are. - Finish structural changes with a repository inventory: search for references to removed entry points, list the supported entry points, verify that current profiles bind every external artifact, and run the behavior and release gates that exercise those public boundaries. @@ -41,8 +41,8 @@ These requirements apply to **all** report tracks; browser-specific rules below - Keep one report implementation in `harness/web_fetch/report.py`. The default `scripts/build_report.py` build uses persistent measured evidence (`~/.browser-eval/evidence/current/` by default, or `WEB_FETCH_EVIDENCE_DIR`) and writes `reports/webfetch/`; the independently bound WCXB build uses `--wcxb` and writes `reports/wcxb/`. Do not create versioned report implementations, compatibility renderers or redirect builds. - Run repository Python only through `make` targets or `uv run --frozen python`. `.python-version`, `uv.lock`, the Makefile and CI are the runtime contract; never fall back to system Python. - Run every publishable provider cohort through `scripts/run_certified_benchmark.py`, which freezes executable task inputs. Existing compiled reference answers stay frozen; pre/post source checks require an explicit `source_verification.reference_mode: live_exact` task declaration. Missing newer acceptance-contract fields never imply a live-reference task. Rerunning an unchanged compiled dataset must not require historical source-qualification raw files or reopen cohort admission; source qualification belongs to adding tasks or changing references/acceptance rules. Direct `run_benchmark.py` output and incomplete cohorts are diagnostic evidence only; never assemble or publish them. -- For the default Web Fetch report, treat `evaluation/current-report.json` as the matrix and publication-target authority. The independent WCXB report uses only `evaluation/wcxb/current-report.json` for its matrix and target. Browser Use and Browser Trajectory Replay have separate contracts under their own `evaluation/` namespaces; never reuse a publication target across reports or tracks. Publishing must use the relevant bound site and expected remote version; do not copy target IDs from prose, old receipts or earlier reports. -- For the complete WebMainBench report, `evaluation/webmainbench-full/current-report.json` is the immutable baseline pre-call profile bound by its cohort certificate. Independently certified variant cohorts are registered in `evaluation/webmainbench-full/`; discover the current variant registry and each variant's profile and certificate from that directory rather than hard-coding names. The method-binding file joins the baseline and all certified variant cohorts through locked profile hashes. Their `publication_state` records frozen pre-call state. The live site, share URL, and expected remote version are bound in `evaluation/webmainbench-full/publication-target.json` together with the baseline and all variant profile hashes; update this target after publication without changing or re-certifying historical calls. +- Select report instances explicitly with `--profile` (or an external `--authority-root` bundle for historical full-cohort formats). Store their matrices, publication targets and mutable version receipts outside Git alongside the frozen evidence. Each report and track owns its own publication binding; never borrow another report's target. +- For the complete WebMainBench report, `evaluation/webmainbench-full/current-report.json` inside the selected external authority bundle is the immutable baseline pre-call profile bound by its cohort certificate. Independently certified variant cohorts are registered in `evaluation/webmainbench-full/`; discover the current variant registry and each variant's profile and certificate from that directory rather than hard-coding names. The method-binding file joins the baseline and all certified variant cohorts through locked profile hashes. Their `publication_state` records frozen pre-call state. The live site, share URL, and expected remote version are bound in `evaluation/webmainbench-full/publication-target.json` together with the baseline and all variant profile hashes; update this target after publication without changing or re-certifying historical calls. - For the current WCXB report, the source-qualified manifest is the entire evaluation population for quality, delivery, and latency. Keep source-invalid cases outside the current manifest, scored evidence, public package, and future provider calls; preserve earlier physical calls only in private historical evidence. The dataset page owns frozen task inputs, source URLs and human reference scope; it must not show measured browser scores. The home page owns aggregate comparisons, the result analysis owns the filterable method-case inventory, and case result pages own full responses. Keep a direct link from each dataset row to a safe frozen-page preview and its human reference. Default result filters must show a nonempty, clearly labeled diagnostic population when one exists, and filtered tables must shrink rather than leave a large blank viewport. - Persist provider calls outside Git under `~/.browser-eval/` (or `BROWSER_EVAL_DATA_HOME`). Git may contain compact benchmark definitions, annotations, independent source evidence and public release metadata, but never measured outputs, execution manifests, provider artifacts, rerun directories, credentials or internal work logs. - Web Fetch keeps one current authoritative logical result per method and task. A declared failure retry retains every physical call, retains total retry execution cost separately from the profile-defined primary latency and uses the final response, never the best response. A proven wrong command, version, scorer, reference or timing is a correction: preserve the prior evidence, supersede the complete declared scope and disclose score, denominator and latency effects. @@ -60,7 +60,7 @@ These requirements apply to **all** report tracks; browser-specific rules below - Keep `research/provider-pricing.json` as a reusable product-price catalog: retain verification dates, commitments, billing units and unknowns, but do not describe one run or report there. New report release history belongs in hash-bound `report-assets/report-changes.json` beside the evidence. `research/report-changes.json` is a legacy fallback while older report owners migrate; do not add a new report's results, prose, review scope or publication URL to it. - Render a report's release history from its bound report asset when present; keep Chinese and English release text together in each entry. Do not duplicate release prose or past measurements in report renderers or exact-string translation maps. Release checks compare structured records with rendered routes rather than matching fixed phrases. Browser Use and trajectory replay keep their existing structured release models. - Use `Browser Use` for the autonomous track and `Browser Trajectory Replay` (Chinese: `轨迹重放`) for the fixed BrowserTool-action track. Their implementation roots are `harness/browser_use/` and `harness/trajectory_replay/`; do not add aliases or compatibility paths using the retired names. -- The public WebArena human-recording replay is isolated under `harness/trajectory_replay/public_webarena/` with its own examples, tests, `evaluation/browser-use-public/current-report.json` and release script. It must not change autonomous `harness/browser_use/` or share a report target or denominator with the repository-curated replay suite. +- The public WebArena human-recording replay is isolated under `harness/trajectory_replay/public_webarena/` with its own examples, tests, external report profile and release script. It must not change autonomous `harness/browser_use/` or share a report target or denominator with the repository-curated replay suite. - Browser Trajectory Replay canonical inputs must not contain run-local snapshot refs. Compile each ref to a hash-bound stable selector while preserving the original ref action semantics; do not silently replace a DOM ref click/fill/select with BrowserTool's Playwright selector fallback because that adds different viewport, overlay and navigation-wait behavior. - Browser Trajectory Replay reports keep the dataset separate from measured verdicts. Every task needs a contract-and-qualified-trajectory page; every result needs a same-task all-browser comparison, a reader-facing failure locator, the exact failed-action or evaluator error, reviewed cause, complete expandable tool responses, and on-demand public evidence. The result inventory stays compact, fixed-height, filterable and linked to a diagnosis dialog. The same-task explorer uses a registry-driven baseline, multi-select comparison browsers and explicit outcome relations; group pass/fail links instead of adding one result column per future browser. Keep Chinese and English task/result routes aligned. - A BrowserTool call that returns success does not prove its intended site mutation occurred. When the official evaluator rejects a completed trajectory for an absent required network event, locate the last plausible trigger action and publish the exact missing-event assertion, but do not mark that action as failed or change the official verdict. Repair asynchronous or position-dependent trajectory inputs only through new qualification evidence and a frozen full-scope comparison rerun. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55833a8e2..152938b9c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,12 +14,46 @@ Include the task ID, method and versions, OS, command and sanitized error. Remov | LexBench Web Fetch task requirements | [Benchmark repository](https://github.com/lexmount/lexbench-webfetch), applied to every method | | Scoring fix | Shared scorer, scenario regressions and a recorded correction reason | | New browser, version or sample size | Configuration or registry; reuse the runner and renderer | +| New report instance | An external instance profile, evidence and report content; reuse its build and release commands | | Reusable logic | `harness/`; `scripts/` stays a thin command entry point | +Use English for identifiers, comments and docstrings. Store report interface text +in language catalogs, and select it through the existing report language contract. +Keep original-language task text, source quotations and multilingual test fixtures. Keep measured counts, host facts and report-specific interpretation out +of shared renderers: read them from the selected profile and bound evidence. +Published historical reports retain their own content and provenance. + Test both accepted and rejected scenarios. Fix scoring rules rather than editing saved verdicts. Refactoring must preserve historical measurements and certificates. Write documentation in English. Keep the [Chinese README](docs/README.zh-CN.md) aligned when setup or usage changes; other docs do not need duplicate translations. Preserve original task text, quotations and frozen acceptance inputs. Reports follow their track's language settings. +## PR and commit titles + +Use `(): ` for PR titles and new commit subjects. The scope is optional; use the module or feature affected, such as `scoring` or `webfetch`. + +Choose the type that describes the change: + +| Type | Change | +| --- | --- | +| `fix` | Correct existing behavior | +| `feat` | Add supported behavior | +| `docs` | Change documentation | +| `refactor` | Restructure code without changing behavior | +| `test` | Add or correct tests | +| `perf` | Improve performance | +| `build` / `ci` | Change packaging, dependencies or automation | +| `chore` | Other maintenance | + +Write the summary in English, start with an imperative verb, and describe the concrete change. Aim for 72 characters or fewer, including the prefix; omit a trailing period. Use `!` before the colon for a breaking change and explain the migration in the description. + +Examples: + +- `fix(scoring): require facts in the returned body` +- `fix(webfetch): disclose build provenance for partial reruns` +- `docs(contributing): define PR and commit title conventions` + +Avoid titles such as `Fix review feedback`, `Update code` or `Misc improvements`; name the behavior or document that changes. A PR title describes the complete diff; each commit subject describes that commit. Update the PR title when its scope changes. If squash merging is used, use a subject that follows the same format. Apply this convention to new work; do not rewrite shared commits solely to rename them. + ## Run the checks ```bash @@ -65,3 +99,7 @@ The archive excludes `.git`, untracked files and local installations. Extract it Optional Claude review requires `ENABLE_CLAUDE_REVIEW=true` and `CLAUDE_CODE_OAUTH_TOKEN`. Ordinary CI needs neither. Review uses disposable GitHub-hosted runners. + +## License + +Contributions use the [Apache License 2.0](LICENSE). Preserve third-party licenses, copyright notices and source attributions described in [THIRD_PARTY](THIRD_PARTY.md). diff --git a/LICENSE b/LICENSE index cfb4b00ca..d64569567 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,202 @@ -MIT License - -Copyright (c) 2026 LexMount - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile index ea8d9bdda..1ae6e3986 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,9 @@ UV ?= uv PY := $(UV) run --frozen python TEST_PY := $(UV) run --frozen --all-extras python WEBMAINBENCH_SAMPLE ?= evaluation/public-webmainbench-200.json -WEBMAINBENCH_PROFILE ?= evaluation/current-report.json +WEBMAINBENCH_PROFILE ?= $(PROFILE) REPORT_LANGUAGE ?= -DIAGNOSTIC_PROFILE ?= evaluation/live-webfetch/current-report.json +DIAGNOSTIC_PROFILE ?= $(PROFILE) .PHONY: setup setup-test import-env runtime-check naming-check test test-python test-ui fixture-smoke ci report report-check popular-sites-report popular-sites-report-check wcxb-release-check resource-image public-benchmark-report public-benchmark-sample webmainbench-run webmainbench-score webmainbench-review webmainbench-assemble browser-use-review browser-use-report browser-use-report-check browser-use-visual-check browser-use-release-check trajectory-replay-report trajectory-replay-report-check trajectory-replay-visual-check trajectory-replay-release-check trajectory-replay-package trajectory-replay-publish webarena-catalog-report webarena-catalog-release-check public-webarena-report public-webarena-report-audit public-webarena-report-release @@ -32,6 +32,7 @@ webfetch-diagnostic-report: runtime-check naming-check: runtime-check $(PY) scripts/check_track_naming.py + $(TEST_PY) -m pytest tests/test_source_language_policy.py -q .PHONY: lint-check lint-check: runtime-check @@ -85,90 +86,90 @@ package-check: runtime-check report: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path is required' >&2; exit 2) - $(PY) scripts/build_report.py --evidence "$(EVIDENCE)" --output reports/webfetch $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) + $(PY) scripts/build_report.py --profile "$(PROFILE)" --evidence "$(EVIDENCE)" --output reports/webfetch $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) report-check: runtime-check - $(PY) scripts/check_report.py reports/webfetch + $(PY) scripts/check_report.py reports/webfetch --profile "$(PROFILE)" $(PY) scripts/check_report_links.py reports/webfetch $(PY) scripts/check_report_revision.py reports/webfetch popular-sites-report: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path is required' >&2; exit 2) - $(PY) scripts/build_report.py --profile evaluation/popular-sites/current-report.json --evidence "$(EVIDENCE)" --output reports/popular-sites $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) + $(PY) scripts/build_report.py --profile "$(PROFILE)" --evidence "$(EVIDENCE)" --output reports/popular-sites $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) popular-sites-report-check: runtime-check - $(PY) scripts/check_report.py reports/popular-sites --profile evaluation/popular-sites/current-report.json + $(PY) scripts/check_report.py reports/popular-sites --profile "$(PROFILE)" $(PY) scripts/check_report_links.py reports/popular-sites wcxb-release-check: runtime-check - $(PY) scripts/check_report.py reports/wcxb + $(PY) scripts/check_report.py reports/wcxb --profile "$(PROFILE)" $(PY) scripts/check_report_links.py reports/wcxb $(PY) scripts/check_report_revision.py reports/wcxb $(UV) run --frozen --extra headless-eval python scripts/check_wcxb_visual.py reports/wcxb browser-use-report: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE="/absolute/path [/absolute/path...]" is required' >&2; exit 2) - $(PY) scripts/build_browser_use_report.py $(foreach path,$(EVIDENCE),--evidence "$(path)") --output reports/browser-use $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) + $(PY) scripts/build_browser_use_report.py $(foreach path,$(EVIDENCE),--evidence "$(path)") --output "$(or $(OUTPUT),reports/browser-use)" $(if $(PROFILE),--profile "$(PROFILE)",) $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) browser-use-review: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path is required' >&2; exit 2) $(PY) scripts/review_browser_use_results.py --evidence "$(EVIDENCE)" browser-use-visual-check: runtime-check - $(PY) scripts/check_browser_use_visual.py reports/browser-use + $(PY) scripts/check_browser_use_visual.py "$(or $(OUTPUT),reports/browser-use)" $(if $(VISUAL_RECEIPT),--receipt "$(VISUAL_RECEIPT)",) browser-use-report-check: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE="/absolute/path [/absolute/path...]" is required' >&2; exit 2) - $(PY) scripts/check_browser_use_report.py reports/browser-use $(foreach path,$(EVIDENCE),--evidence "$(path)") --require-complete - $(PY) scripts/check_report_links.py reports/browser-use + $(PY) scripts/check_browser_use_report.py "$(or $(OUTPUT),reports/browser-use)" $(if $(PROFILE),--profile "$(PROFILE)",) $(foreach path,$(EVIDENCE),--evidence "$(path)") --require-complete + $(PY) scripts/check_report_links.py "$(or $(OUTPUT),reports/browser-use)" browser-use-release-check: browser-use-report-check $(MAKE) browser-use-visual-check trajectory-replay-report: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path is required' >&2; exit 2) - $(PY) scripts/build_webarena_replay_report.py --results "$(EVIDENCE)" $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) + $(PY) scripts/build_webarena_replay_report.py --results "$(EVIDENCE)" --output "$(or $(OUTPUT),reports/trajectory-replay)" $(if $(PROFILE),--profile "$(PROFILE)",) $(if $(REVIEW),--review "$(REVIEW)",) $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) trajectory-replay-report-check: runtime-check - $(PY) scripts/check_webarena_replay_report.py reports/trajectory-replay + $(PY) scripts/check_webarena_replay_report.py "$(or $(OUTPUT),reports/trajectory-replay)" $(if $(PROFILE),--profile "$(PROFILE)",) trajectory-replay-visual-check: runtime-check - $(PY) scripts/check_webarena_replay_visual.py reports/trajectory-replay + $(PY) scripts/check_webarena_replay_visual.py "$(or $(OUTPUT),reports/trajectory-replay)" $(if $(VISUAL_RECEIPT),--receipt "$(VISUAL_RECEIPT)",) trajectory-replay-release-check: trajectory-replay-report-check $(MAKE) trajectory-replay-visual-check trajectory-replay-package: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path/effective-results.json is required' >&2; exit 2) - $(PY) scripts/package_webarena_replay_artifact_site.py --evidence "$(EVIDENCE)" + $(PY) scripts/package_webarena_replay_artifact_site.py --evidence "$(EVIDENCE)" --report "$(or $(OUTPUT),reports/trajectory-replay)" $(if $(PROFILE),--profile "$(PROFILE)",) $(if $(REVIEW),--review "$(REVIEW)",) $(if $(ARCHIVE),--output "$(ARCHIVE)",) $(if $(ATTESTATION),--attestation "$(ATTESTATION)",) $(if $(VISUAL_RECEIPT),--visual-receipt "$(VISUAL_RECEIPT)",) trajectory-replay-publish: runtime-check - $(PY) scripts/publish_webarena_replay_artifact_site.py + $(PY) scripts/publish_webarena_replay_artifact_site.py --profile "$(PROFILE)" webarena-catalog-report: runtime-check @test -n "$(EVIDENCE_ROOT)" -a -n "$(LEDGER)" -a -n "$(SCOPE)" -a -n "$(CATALOG)" -a -n "$(SUITE)" -a -n "$(SITE_MAP)" -a -n "$(OUTPUT)" -a -n "$(ARCHIVE)" || (echo 'EVIDENCE_ROOT, LEDGER, SCOPE, CATALOG, SUITE, SITE_MAP, OUTPUT and ARCHIVE are required' >&2; exit 2) $(PY) scripts/build_webarena_catalog_report.py --evidence-root "$(EVIDENCE_ROOT)" --ledger "$(LEDGER)" --scope "$(SCOPE)" --catalog "$(CATALOG)" --portable-suite "$(SUITE)" --site-map "$(SITE_MAP)" --output "$(OUTPUT)" --archive "$(ARCHIVE)" webarena-catalog-release-check: runtime-check - $(PY) scripts/check_webarena_catalog_release.py $(if $(DATA_HOME),--data-home "$(DATA_HOME)",) + $(PY) scripts/check_webarena_catalog_release.py --profile "$(PROFILE)" --comparison-release "$(COMPARISON_RELEASE)" $(if $(DATA_HOME),--data-home "$(DATA_HOME)",) public-webarena-report: runtime-check - $(PY) scripts/release_webarena_trajectory_report.py $(if $(FORMAL_ROOT),--formal-root "$(FORMAL_ROOT)",) + $(PY) scripts/release_webarena_trajectory_report.py --profile "$(PROFILE)" $(if $(FORMAL_ROOT),--formal-root "$(FORMAL_ROOT)",) public-webarena-report-audit: runtime-check $(TEST_PY) -m pytest tests/trajectory_replay/public_webarena -q - $(PY) scripts/release_webarena_trajectory_report.py $(if $(FORMAL_ROOT),--formal-root "$(FORMAL_ROOT)",) + $(PY) scripts/release_webarena_trajectory_report.py --profile "$(PROFILE)" $(if $(FORMAL_ROOT),--formal-root "$(FORMAL_ROOT)",) public-webarena-report-release: runtime-check - $(PY) scripts/release_webarena_trajectory_report.py --publish + $(PY) scripts/release_webarena_trajectory_report.py --profile "$(PROFILE)" --publish resource-image: docker build -f examples/web_fetch/docker/resource.Dockerfile -t lex-web-fetch-resource:local . public-benchmark-report: runtime-check @test -n "$(EVIDENCE)" || (echo 'EVIDENCE=/absolute/path/to/reviewed-public-benchmark-evidence is required' >&2; exit 2) - $(PY) scripts/build_report.py --evidence "$(EVIDENCE)" --output reports/webfetch-public-benchmarks $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) - $(PY) scripts/check_report.py reports/webfetch-public-benchmarks + $(PY) scripts/build_report.py --profile "$(PROFILE)" --evidence "$(EVIDENCE)" --output reports/webfetch-public-benchmarks $(if $(REPORT_LANGUAGE),--language "$(REPORT_LANGUAGE)",) $(if $(filter 1,$(ENGLISH)),--include-english,) + $(PY) scripts/check_report.py reports/webfetch-public-benchmarks --profile "$(PROFILE)" $(PY) scripts/check_report_links.py reports/webfetch-public-benchmarks public-benchmark-sample: runtime-check diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..f24d1ce2e --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Browser Eval +Copyright 2026 LexMount diff --git a/README.md b/README.md index 98a3ab284..586f61ee4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,11 @@
-Browser Eval evaluation workflow - -# Browser Eval - -### Run the same tasks. Keep the evidence. - -An evaluation harness for web fetching, recorded browser actions and autonomous agents. +Browser Eval — Same tasks. Clear comparisons. **English** · [简体中文](docs/README.zh-CN.md) [![CI](https://github.com/lexmount/browser-eval/actions/workflows/ci.yml/badge.svg)](https://github.com/lexmount/browser-eval/actions/workflows/ci.yml) -[![License: MIT](https://img.shields.io/badge/code-MIT-51735b)](LICENSE) +[![License: Apache 2.0](https://img.shields.io/badge/code-Apache--2.0-51735b)](LICENSE) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-356a91)](pyproject.toml) [Quickstart](#quickstart) · [Benchmarks](docs/benchmarks.md) · [Reports](#read-and-share-a-report) · [Contributing](CONTRIBUTING.md) @@ -176,7 +170,7 @@ make ci Historical pages and measured outputs live outside Git. For reproducing past runs, see [reference inputs](docs/reference-inputs.md). For code layout and release internals, see [architecture](docs/architecture.md). -Project code is [MIT licensed](LICENSE). [THIRD_PARTY](THIRD_PARTY.md) covers data rights and attribution; [SECURITY](SECURITY.md) covers credentials and untrusted content. +Project code is [Apache 2.0 licensed](LICENSE). [THIRD_PARTY](THIRD_PARTY.md) covers data rights and attribution; [SECURITY](SECURITY.md) covers credentials and untrusted content. ## Cite Browser Eval diff --git a/THIRD_PARTY.md b/THIRD_PARTY.md index 52ead281a..c0b4ad484 100644 --- a/THIRD_PARTY.md +++ b/THIRD_PARTY.md @@ -1,6 +1,6 @@ # Third-party content and licenses -The root MIT license covers project-owned code and documentation. It does not replace licenses for third-party code, pages, images, documents or datasets. Existing copyright notices and directory licenses remain in effect. +The root Apache-2.0 license covers project-owned code and documentation. It does not replace licenses for third-party code, pages, images, documents or datasets. Existing copyright notices and directory licenses remain in effect. | Content | Source and binding | Distribution boundary | | --- | --- | --- | diff --git a/configs/providers.headless.json b/configs/providers.headless.json index ef48847f7..00375088a 100644 --- a/configs/providers.headless.json +++ b/configs/providers.headless.json @@ -5,7 +5,8 @@ "engine": "kitesurf", "account_id": "${CLOUDFLARE_ACCOUNT_ID}", "api_token": "${CLOUDFLARE_API_TOKEN}", - "wait_until": "networkidle2" + "wait_until": "networkidle2", + "version_source": "reported" }, "lightpanda": { "type": "headless_markdown", @@ -23,24 +24,28 @@ "type": "headless_markdown", "engine": "moli", "binary": "${MOLI_1_0_6_BINARY:-moli-1.0.6}", - "expected_version": "1.0.6" + "expected_version": "1.0.6", + "label": "Moli 1.0.6" }, "moli": { "type": "headless_markdown", "engine": "moli", "binary": "${MOLI_BINARY:-moli}", - "expected_version": "1.1.5" + "expected_version": "1.1.5", + "label": "Moli" }, "moli-1.1.7": { "type": "headless_markdown", "engine": "moli", "binary": "${MOLI_1_1_7_BINARY:-moli-1.1.7}", - "expected_version": "1.1.7" + "expected_version": "1.1.7", + "label": "Moli 1.1.7" }, "moli-pr643": { "type": "headless_markdown", "engine": "moli", - "binary": "${MOLI_PR643_BINARY}" + "binary": "${MOLI_PR643_BINARY}", + "label": "Moli 1.1.7 · PR #643" }, "moli-release": { "type": "moli_webfetch", @@ -63,13 +68,21 @@ "type": "headless_markdown", "engine": "moli", "binary": "${MOLI_1_1_3_BINARY:-moli-1.1.3}", - "expected_version": "1.1.3" + "expected_version": "1.1.3", + "label": "Moli 1.1.3" }, "chromium": { "type": "headless_markdown", "engine": "chromium", "binary": "${CHROMIUM_BINARY}", "resource_binary": "/usr/bin/chromium" + }, + "moli-1.1.11": { + "type": "headless_markdown", + "engine": "moli", + "binary": "${MOLI_1_1_11_BINARY:-moli-1.1.11}", + "expected_version": "1.1.11", + "label": "Moli 1.1.11" } }, "default_cohort": "main", diff --git a/configs/report-templates.json b/configs/report-templates.json index 10636a809..36693ead9 100644 --- a/configs/report-templates.json +++ b/configs/report-templates.json @@ -4,37 +4,50 @@ "webfetch": { "renderer": "harness/web_fetch/report.py", "entrypoint": "scripts/build_report.py", - "profile": "evaluation/current-report.json", "example": "https://artifact-site.lexmount.cn/s/p3sFcZrww_nx?version=ver_8ea488f5197c4b57a94c0e7987587e26", - "example_profile": "evaluation/wcxb/current-report.json", - "build": "uv run --frozen python scripts/build_report.py --profile evaluation/current-report.json --evidence /absolute/path --output reports/webfetch", + "build": "uv run --frozen python scripts/build_report.py --profile /absolute/instance-profile.json --evidence /absolute/path --output reports/webfetch", "check": "make report-check", - "wcxb_profile": "evaluation/wcxb/current-report.json", - "wcxb_build": "uv run --frozen python scripts/build_report.py --profile evaluation/wcxb/current-report.json --evidence /absolute/path --output reports/wcxb", - "wcxb_check": "make wcxb-release-check", + "wcxb_build": "uv run --frozen python scripts/build_report.py --profile /absolute/instance-profile.json --evidence /absolute/path --output reports/wcxb", + "wcxb_check": "make wcxb-release-check PROFILE=/absolute/instance-profile.json", + "wcxb_package": "uv run --frozen python scripts/publish_wcxb_artifact_site.py --package-only --profile /absolute/instance-profile.json --source /absolute/new-report --work /absolute/package", "tests": "tests/test_headless_report.py", - "variants": ["certified", "wcxb", "local-audit", "diagnostic-cohorts"], - "diagnostic_build": "make webfetch-diagnostic-report DIAGNOSTIC_PROFILE=evaluation/live-webfetch/current-report.json OUTPUT=/absolute/new-output", - "diagnostic_profile": "evaluation/live-webfetch/current-report.json", - "diagnostic_check": "uv run --frozen python scripts/check_diagnostic_report.py /absolute/output --profile evaluation/live-webfetch/current-report.json", + "variants": [ + "certified", + "wcxb", + "local-audit", + "diagnostic-cohorts", + "published-snapshot-audit" + ], + "diagnostic_build": "make webfetch-diagnostic-report DIAGNOSTIC_PROFILE=/absolute/instance-profile.json OUTPUT=/absolute/new-output", + "diagnostic_check": "uv run --frozen python scripts/check_diagnostic_report.py /absolute/output --profile /absolute/instance-profile.json", "diagnostic_example": "tests/test_report_templates.py:diagnostic_example", - "diagnostic_example_check": "uv run --frozen python -m unittest tests.test_report_templates" + "diagnostic_example_check": "uv run --frozen python -m unittest tests.test_report_templates", + "snapshot_audit_build": "uv run --frozen python scripts/build_report.py --profile /absolute/profile.json --evidence /absolute/published-snapshot --output /absolute/new-report --language zh", + "snapshot_audit_check": "uv run --frozen python scripts/check_report.py /absolute/new-report --profile /absolute/profile.json", + "snapshot_audit_tests": "tests/test_published_snapshot_rescoring.py", + "snapshot_audit_example": "tests/test_published_snapshot_report.py:example", + "snapshot_audit_example_check": "uv run --frozen python -m pytest tests/test_published_snapshot_report.py tests/test_published_snapshot_rescoring.py", + "snapshot_audit_release_check": "uv run --frozen python scripts/check_report.py /absolute/new-report --profile /absolute/profile.json --visual-receipt /absolute/private/visual.json", + "snapshot_audit_package": "uv run --frozen python scripts/package_artifact_hub.py /absolute/new-report /absolute/package.zip --snapshot-audit", + "snapshot_audit_publish": "uv run --frozen python scripts/publish_artifact_hub.py --create --profile /absolute/publication-target.json --source /absolute/new-report --archive /absolute/package.zip --receipt /absolute/receipt.json --operation-journal /absolute/pending.json --snapshot-audit --visibility private" }, "trajectory-replay": { "renderer": "harness/trajectory_replay/webarena_report.py", "entrypoint": "scripts/build_webarena_replay_report.py", - "profile": "evaluation/trajectory-replay/webarena-verified-full/current-report.json", - "build": "make trajectory-replay-report EVIDENCE=/absolute/path/results.json", - "check": "make trajectory-replay-release-check", - "tests": "tests/trajectory_replay/test_webarena_report.py" + "build": "uv run --frozen python scripts/build_webarena_replay_report.py --profile /absolute/instance-profile.json --results /absolute/results.json --review /absolute/review.json --output /absolute/new-report", + "check": "uv run --frozen python scripts/check_webarena_replay_report.py /absolute/new-report --profile /absolute/instance-profile.json", + "tests": "tests/trajectory_replay/test_webarena_report.py", + "example": "https://artifact-site.lexmount.cn/s/EFLrNgujgsVc?version=ver_c0f0693e9c08401bb4d2b88393234e55", + "package": "uv run --frozen python scripts/package_webarena_replay_artifact_site.py --report /absolute/new-report --profile /absolute/instance-profile.json --evidence /absolute/results.json --review /absolute/review.json --output /absolute/package.zip --attestation /absolute/attestation.json --visual-receipt /absolute/visual.json" }, "browser-use": { "renderer": "harness/browser_use/report.py", "entrypoint": "scripts/build_browser_use_report.py", - "profile": "evaluation/browser-use/current-report.json", - "build": "make browser-use-report EVIDENCE=/absolute/path", - "check": "make browser-use-release-check EVIDENCE=/absolute/path", - "tests": "tests/browser_use/test_report.py" + "build": "uv run --frozen python scripts/build_browser_use_report.py --profile /absolute/instance-profile.json --evidence /absolute/evidence --output /absolute/new-report", + "check": "uv run --frozen python scripts/check_browser_use_report.py /absolute/new-report --profile /absolute/instance-profile.json --evidence /absolute/evidence --require-complete", + "tests": "tests/browser_use/test_report.py", + "package": "uv run --frozen python scripts/package_browser_use_artifact_hub.py --report /absolute/new-report --profile /absolute/instance-profile.json --evidence /absolute/evidence --output /absolute/package.zip --attestation /absolute/attestation.json --visual-receipt /absolute/visual.json", + "example": "https://artifact-hub.app.lexmount.com/s/7l77EeXPRfml?version=ver_ce4dd70fde2e444ab18d24bba3ee0e93" } } } diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index 59323af16..c5e2ad2a0 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -1,17 +1,11 @@
-Browser Eval evaluation workflow - -# Browser Eval - -### 执行同一组任务,留下可核查的证据。 - -用于网页抓取、固定浏览器操作和自主 Agent 的评测脚手架。 +Browser Eval — Same tasks. Clear comparisons. [English](../README.md) · **简体中文** [![CI](https://github.com/lexmount/browser-eval/actions/workflows/ci.yml/badge.svg)](https://github.com/lexmount/browser-eval/actions/workflows/ci.yml) -[![License: MIT](https://img.shields.io/badge/code-MIT-51735b)](../LICENSE) +[![License: Apache 2.0](https://img.shields.io/badge/code-Apache--2.0-51735b)](../LICENSE) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-356a91)](../pyproject.toml) [快速开始](#快速开始) · [Benchmark 接入](benchmarks.md) · [报告](#查看与分享报告) · [参与贡献](../CONTRIBUTING.md) @@ -176,7 +170,7 @@ make ci 历史网页和测量输出保存在 Git 之外。复现历史运行见[参考输入](../docs/reference-inputs.md),代码结构和发布内部流程见[架构说明](../docs/architecture.md)。 -项目代码采用 [MIT 许可](../LICENSE)。数据权利和署名见[第三方说明](../THIRD_PARTY.md),凭据和不可信内容处理见[安全说明](../SECURITY.md)。 +项目代码采用 [Apache 2.0 许可](../LICENSE)。数据权利和署名见[第三方说明](../THIRD_PARTY.md),凭据和不可信内容处理见[安全说明](../SECURITY.md)。 ## 引用 Browser Eval diff --git a/docs/assets/overview.png b/docs/assets/overview.png new file mode 100644 index 000000000..1fd9b64aa Binary files /dev/null and b/docs/assets/overview.png differ diff --git a/docs/assets/overview.svg b/docs/assets/overview.svg deleted file mode 100644 index a7c57a2ed..000000000 --- a/docs/assets/overview.svg +++ /dev/null @@ -1,22 +0,0 @@ - -Browser Eval -Evaluation harness: TASK INPUTS — LexBench · WebMainBench · WCXB · WebArena; EXECUTION — Browsers · Services · Agents; DELIVERY — Saved evidence · Scores · Reports - - -EVALUATION HARNESS -Browser Eval - -TASK INPUTS -LexBench · WebMainBench -WCXB · WebArena - - -EXECUTION -Browsers · Services -Agents - - -DELIVERY -Saved evidence · Scores -Reports - diff --git a/docs/benchmarks.md b/docs/benchmarks.md index b7b9cce74..cab5da6cb 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -41,7 +41,7 @@ uv run --frozen browser-harness-eval run \ --provider-config configs/providers.quickstart.json --providers http ``` -This uses the existing dataset interface. `uv sync --frozen` installs LexBench from the exact Git commit pinned in `pyproject.toml` and `uv.lock`. The pinned commit is merged into LexBench’s main branch. uv retrieves the Git source, builds a wheel and installs the Python package; Git must be available, but no sibling checkout is required. The input checker verifies frozen bytes; a formal live-site comparison still requires the [certified runner](../harness/web_fetch/README.md), independent reference validation before and after calls, and the selected track's release gates. Ordinary CLI runs are diagnostic. +This uses the existing dataset interface. `uv sync --frozen` installs LexBench from the exact Git commit pinned in `pyproject.toml` and `uv.lock`. uv retrieves the Git source, builds a wheel and installs the Python package; Git must be available, but no sibling checkout is required. The input checker verifies frozen bytes; a formal live-site comparison still requires the [certified runner](../harness/web_fetch/README.md), independent reference validation before and after calls, and the selected track's release gates. Ordinary CLI runs are diagnostic. ## Historical bindings and new revisions @@ -50,3 +50,155 @@ The four initial LexBench suites preserve the original catalogs and source-input Change task requirements in LexBench, freeze a new dataset/catalog/manifest version there, and select that version in a new experiment profile. Fix execution defects in Browser Eval and scoring defects in LexBench; keep accepted/rejected scenario controls with their owner. Preserve prior measurements and apply corrections across the declared affected scope. The retained `harness/lexbench/` module concerns historical headless-browser comparisons. It is unrelated to the `lexbench-webfetch` input package and remains an execution/report adapter. + +## Report instances + +Each report track uses its maintained build, check and package entry points. +Browser Use and Browser Trajectory Replay accept an instance profile at each boundary. Keep the profile and measured evidence outside +Git. Select a new output directory; changing participants, sample size or report +content does not require another renderer or command script. + +The native Web Fetch report also accepts an external profile. Bind the frozen +dataset, complete participant matrix, execution settings and evidence hashes in +that profile. Put report prose in the evidence tree and bind it through +`comparison.report_interpretation`; a report refresh does not require changing +Python strings. The normal build and check retain certification and review gates. +The diagnostic build is a separate evidence state and does not certify results. + +```sh +uv run --frozen python scripts/build_report.py \ + --profile /absolute/instance/profile.json \ + --evidence /absolute/instance/evidence --output /absolute/instance/report +uv run --frozen python scripts/check_report.py \ + /absolute/instance/report --profile /absolute/instance/profile.json +``` + +A profile stored outside Git has the same validation rules as a repository profile. +Repository defaults select the maintained published report; they are optional +convenience paths, not a requirement to commit new report instances. + +Browser Use profiles register models, browser labels and measurement controls. +`task_selection.path` and `task_selection.sha256` bind the selection file; its hash +must match `selection_sha256`. The selection declares its own `metadata.tier_counts`. +`official_dataset.path` and `official_dataset.sha256` can bind the exact historical +official task file, matching `official_data_checksum` and the recorded calls. +This allows rebuilding an older measurement after the installed benchmark has +changed. `failure_cause_review.source_path` may name an external, hash-bound review. +Keep original attempts and task contracts unchanged. + +An optional `report_history` object binds a JSON file through `path` and `sha256`. +Relative paths use the authority root; absolute paths can select external content. +A new instance without this binding has no release history. It never inherits the +maintained report's releases or review rounds. Build, check and package use the +same binding. + +```sh +uv run --frozen python scripts/build_browser_use_report.py \ + --profile /absolute/instance/profile.json \ + --evidence /absolute/model-evidence --output /absolute/instance/report +uv run --frozen python scripts/check_browser_use_report.py \ + /absolute/instance/report --profile /absolute/instance/profile.json \ + --evidence /absolute/model-evidence --require-complete +uv run --frozen python scripts/package_browser_use_artifact_hub.py \ + --report /absolute/instance/report --profile /absolute/instance/profile.json \ + --evidence /absolute/model-evidence --output /absolute/instance/report.zip \ + --attestation /absolute/instance/attestation.json \ + --visual-receipt /absolute/instance/visual.json +``` + +Repeat `--evidence` for additional model cohorts. The renderer derives comparison +counts and failure examples from the selected results. Missing task-type or +execution metadata stays unavailable; it is never inferred from historical IDs. + +Trajectory Replay profiles bind the dataset, participant registry, execution +protocol and reference validity. The failure review must bind the exact saved +results. Without an authored content binding, the maintained renderer generates +an interpretation from the current results, including complete failure +distributions and specimen links. An optional authored interpretation uses +`--content`, `--content-bindings` and `--interpretation-audit`; these inputs must +belong to the selected evidence. + +```sh +uv run --frozen python scripts/build_webarena_replay_report.py \ + --profile /absolute/instance/profile.json --results /absolute/results.json \ + --review /absolute/failure-review.json --output /absolute/instance/report +uv run --frozen python scripts/check_webarena_replay_report.py \ + /absolute/instance/report --profile /absolute/instance/profile.json +uv run --frozen python scripts/package_webarena_replay_artifact_site.py \ + --report /absolute/instance/report --profile /absolute/instance/profile.json \ + --evidence /absolute/results.json --review /absolute/failure-review.json \ + --output /absolute/instance/report.zip \ + --attestation /absolute/instance/attestation.json \ + --visual-receipt /absolute/instance/visual.json +``` + +Both packers run integrity, link and browser checks before installing an archive. +The replay packer automatically uses the existing compact representation above +its file-count limit. It verifies every task/result page and evidence payload +against the uncompressed report, then exercises exported routes in every saved +language. Compression preserves all logical results and physical-call evidence. + +The Make targets accept `PROFILE`, `OUTPUT` and `VISUAL_RECEIPT` for these tracks; +replay build/package targets also accept `REVIEW`, and packaging accepts `ARCHIVE` +and `ATTESTATION`. Packaging creates a local archive and does not publish it. + +For WCXB, select a profile-bound evidence directory and a fresh output with +`scripts/build_report.py --profile /absolute/instance/profile.json`. Keep its +`report-content-binding.json` beside the profile. `providers` declares the matrix; +`participants` can supply labels, bilingual routes, interface examples and account +requirements. An optional `paired_comparison` names `baseline` and `candidate` +participants. A declared timing correction still requires complete paired evidence; +ordinary cohorts do not inherit another report’s timing or optional-feature claims. Run +`scripts/check_report.py` with that profile and `scripts/check_wcxb_visual.py` +against the output. The existing publication entry point also supports local +packaging without credentials or a network upload: + +```sh +uv run --frozen python scripts/publish_wcxb_artifact_site.py --package-only \ + --profile /absolute/instance/profile.json \ + --source /absolute/instance/report --work /absolute/instance/package +``` + +### Re-score a published snapshot + +A `published-snapshot-audit` profile rebuilds a saved Web Fetch report through the +current renderer. Bind its complete exported file manifest, original and corrected +results, historical verdicts, initial recomputation, scoring receipt, review, +interpretation and diagram under `inputs`. An optional bound `method_registry` +provides historical participant IDs, engines and labels; `release_history` supplies +the report's structured history. Keep these instance files outside Git. + +`score_published_snapshot` verifies the full task-by-method matrix, preserves call +identities and timing, and computes scores without contacting providers. It accepts +an exact hash-verified recovered original, otherwise identifies a privacy-projected +input separately. A projected file alone is not evidence that redaction changed a +verdict. Compare the old scorer on the same bytes before assigning that cause. + +Build with `scripts/build_report.py --profile --evidence +--output --language zh`. This historical format supports Chinese and +provides an English not-generated notice. Check with `scripts/check_report.py + --profile --visual-receipt `, then package +through `scripts/package_artifact_hub.py --snapshot-audit`. +Run these Python entries through `uv run --frozen python`. The mode retains the +original routes and metric populations and labels the new report as a saved-input +audit; it does not certify new calls or replace the original release. The ordinary +packaging path rejects this evidence state without the explicit audit flag. + +Publish through `scripts/publish_artifact_hub.py --snapshot-audit` with a separate +publication target, receipt and operation journal. Use `--create` for a new report +and set visibility explicitly. A later update uses that report's own site and +expected version. Keep the original report's target unchanged. + +### Participant interface metadata + +A report can bind participant documentation through +`comparison.method_registry`. Each participant selects its execution `engine` +and label. Optional `report_access` contains `account`, `configuration`, +`env_vars` and `links`; optional `report_interface` contains `entry`, `auth`, +`params`, `fields`, `request`, `source` and `notes`. These objects follow the +shared interface renderer's existing format. Use them for cohort-specific +instructions and examples. Measured version fields and binary hashes remain +part of each saved call; a participant name is not a version receipt. + +Shared adapter documentation describes interfaces by engine. Adding a version +or renaming a participant requires data changes, without another renderer entry. diff --git a/docs/dependencies.md b/docs/dependencies.md index 035f2a917..ca20054bc 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -61,7 +61,7 @@ Choose the scorer named by the experiment's [profile and correction records](../ | Other inputs | Setup and version record | | --- | --- | -| [WCXB](https://github.com/Murrough-Foley/web-content-extraction-benchmark) | [Current profile](../evaluation/wcxb/current-report.json) | +| [WCXB](https://github.com/Murrough-Foley/web-content-extraction-benchmark) | External run profile supplied with `--profile` | | WebArena sites and human recordings | [Deployment and replay guide](webarena-fixed-trajectory-reproduction.md) | | Historical source pages | Import approved copies using [reference-input validation](reference-inputs.md) | diff --git a/docs/metrics.md b/docs/metrics.md index b0548b578..ef358c91e 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -21,8 +21,11 @@ A returned file URL is not a delivered file. Dynamic list checks measure structu | Body precision | How much returned text belongs to that reference | | Body F1 | Harmonic mean of precision and recall | | Mean / P50 / P95 latency | In the default ranking, timing of task-passing calls only; failures still retain elapsed time in their records | +| All-call mean / P50 / P95 | The complete call population, including failed calls and timeouts; exposed as `mean_all_attempts_ms`, `all_attempts_p50_ms` and `all_attempts_p95_ms` in scorer summaries | | Failure class | Request error or unmet task requirement; open the detail for the full error | Body metrics require a complete, comparable reference. A failed call scores zero; a missing reference leaves the metric unavailable. Check each report's stated timing population before comparing it with another report. Outputs have SHA-256 hashes. Rebuilding or editing a report must preserve the saved responses and scores. Metric labels come from `report_components.py`; the report does not show a pass rate conditioned only on successful requests. + +Formal cohorts reject selected tasks whose acceptance rules are unresolved before capturing references or calling providers. A repair run may retain passed results and rerun only failures. Rescore the complete selected population under one scoring contract, retain every physical call, and disclose each executable fingerprint and its selected result count. A cross-build cumulative result is not a full run by one build. Offline file rescoring reads the retained file bytes and applies the current file requirements while preserving the original call metadata. diff --git a/docs/report-hosting.md b/docs/report-hosting.md index c90a9dbc9..848f1401e 100644 --- a/docs/report-hosting.md +++ b/docs/report-hosting.md @@ -2,6 +2,15 @@ Publish the generated `report/` directory. Keep the rest of the run directory locally so you can rebuild and audit it. +## Report instances + +Keep report profiles, evidence, authored content and publication receipts outside +the checkout. Pass `--profile /absolute/instance-profile.json` to report build, +check and package commands, or `PROFILE=/absolute/instance-profile.json` to their +Make targets. A new instance reuses the same commands with different inputs. +Historical full-cohort formats use an explicit `--authority-root` bundle that +retains their original relative paths and hash-bound profile bytes. + ## Report languages The Web Fetch CLI defaults to English. `run --language zh` generates Chinese only; `report --language en` rebuilds both editions and opens English. A rebuild without `--language` keeps the saved choice, including Chinese-only reports created by older versions. diff --git a/docs/webarena-fixed-trajectory-reproduction.md b/docs/webarena-fixed-trajectory-reproduction.md index 94996e366..df6586392 100644 --- a/docs/webarena-fixed-trajectory-reproduction.md +++ b/docs/webarena-fixed-trajectory-reproduction.md @@ -11,7 +11,7 @@ WebArena-Verified 1.2.3 supplies tasks and evaluators as a dependency. Official For a new experiment, run `uv run --frozen python scripts/lock_webarena_images.py --output /absolute/path/new-images.json`. It reuses environment site definitions, verifies registry digests and manifest bytes, and writes only a new file. Historical experiments use their bound image locks; resolving `latest` again does not reproduce an old image. -Git alone cannot restore a previous run byte for byte. Screenshots, HAR, attempts, manifests and summaries remain outside Git. `evaluation/browser-use-public/current-report.json` retains current public results and external evidence hashes to verify report bindings, not to replace a new run. +Git alone cannot restore a previous run byte for byte. Screenshots, HAR, attempts, manifests and summaries remain outside Git. The external report profile binds its result population and evidence hashes; pass it explicitly with `--profile` when packaging or publishing. The public recording inventory contains 179 candidates: 30 `eligible` and 149 `skipped`. Formal runners execute only eligible tasks; candidate count is not the run denominator. diff --git a/evaluation/browser-use-public/current-report.json b/evaluation/browser-use-public/current-report.json deleted file mode 100644 index 17ecde279..000000000 --- a/evaluation/browser-use-public/current-report.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "schema": "lexmount_webarena_fixed_trajectory_current_report/1", - "title": "WebArena 固定轨迹回放评测", - "evaluation_kind": "fixed_trajectory_replay", - "agentic_browser_use": false, - "benchmark": "WebArena", - "task_count_per_engine": 30, - "engines": [ - "chromium", - "moli" - ], - "run_id": "342ae6e73a88-c10851daf", - "suite_sha256": "ed2832f9c061726cd856d2949313f6a543c770277122eb42dd38cd0a8d27fe0c", - "evidence": { - "location": "outside_git", - "manifest_sha256": "76604ab681509c473aa6bf93ed97e847e1f292eb745168c302ce56c104d63a78", - "summary_sha256": "e148bcec6d6c523440d9d70a028b84f5835e8af4339612e64bfc94a0e43b0d80" - }, - "results": { - "chromium": { - "passes": 29 - }, - "moli": { - "passes": 15 - } - }, - "publication": { - "base_url": "https://artifact-hub.app.lexmount.com", - "site_slug": "G_3dLaZQ1-4H", - "version_id": "ver_833ab7a921864bcdbc13fce1bb5ee640", - "share_url": "https://artifact-hub.app.lexmount.com/v/EpR29VQ99CAgQsT-xdCSX8yg_O7DlWyL", - "submitted_files_verified": 69 - } -} diff --git a/evaluation/browser-use/current-report.json b/evaluation/browser-use/current-report.json deleted file mode 100644 index 7ff100772..000000000 --- a/evaluation/browser-use/current-report.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "schema": "lex_browser_use_current_report/2", - "benchmark": "WebArena-Verified", - "benchmark_version": "1.2.3", - "task_count": 90, - "selection_sha256": "2cb716552e36df7535e83ed6d2cf3f84283e3aca86072817cd6e039a38547c7b", - "official_data_checksum": "d65275660814663375028e9017e1f929e3c38321041b125795e2713b52243d30", - "engines": [ - "chromium", - "moli" - ], - "engine_labels": { - "chromium": "Chromium", - "moli": "Moli 1.1.3" - }, - "baseline_engine": "chromium", - "models": [ - "gpt-5.6-sol", - "gpt-6-astra" - ], - "model_labels": { - "gpt-5.6-sol": "GPT-5.6 Sol", - "gpt-6-astra": "GPT-6 Astra" - }, - "reasoning_effort": "low", - "max_agent_steps": 40, - "max_task_executions": 2, - "logical_result": "score authority follows the frozen run manifest; aggregate all execution latency and cost", - "model_transport_retries": 1, - "scoring_authority": "unmodified official programmatic evaluator", - "review_kind": "deterministic evidence integrity audit", - "independent_semantic_review": false, - "failure_cause_review": { - "required_for_publication": true, - "source_path": "research/browser-use-failure-cause-review.json", - "source_sha256": "a11134919f5e7b9a65c601374211e20e7e940f82e6f363ed025c49b52336e721" - }, - "trajectory_replay_bridge": { - "source_path": "research/browser-use-trajectory-replay-bridge.json", - "source_sha256": "731c04735e6b58d271d4ed5d43514300060c3ecfca0f54d7bc826c1300dd2ac5" - }, - "publication": { - "base_url": "https://artifact-hub.app.lexmount.com", - "site_slug": "7l77EeXPRfml", - "share_url": "https://artifact-hub.app.lexmount.com/v/Ghn1BR6PCy-njsCMqCe2LRIJ-eyJEJoa", - "expected_version": "ver_ce4dd70fde2e444ab18d24bba3ee0e93", - "state": "verified" - } -} diff --git a/evaluation/current-report.json b/evaluation/current-report.json deleted file mode 100644 index 4569c1466..000000000 --- a/evaluation/current-report.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "schema_version": 1, - "dataset_id": "webmainbench-545-preselected-200-six-methods", - "comparison_kind": "webmainbench_frozen_html_to_markdown", - "case_count": 200, - "case_ids_sha256": "2a2ad7b561a33cef1063f2c16d1ddbc6ff57061567c827e772453d8a6562dc8e", - "providers": [ - "kitesurf", - "lightpanda", - "obscura", - "moli", - "moli-1.1.7", - "chromium" - ], - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "max_failure_retries": 1, - "reference_binding_mode": "preselected_webmainbench_545_sample_200_official_frozen_html_and_groundtruth_content", - "source_dataset_sha256": "0efaa4b49a45e320a27fe6e5a0b6aad5b57259fc3321ac3448519cacc74c537e", - "sample_manifest": "evaluation/public-webmainbench-200.json", - "sample_manifest_sha256": "eea643afddd35f3ae4ed85b689b3a89f6a503f8fce878d546fa56c97c26f8044", - "official_scorer_commit": "9d991bdc00c57b57521499494d96be85c31317ba", - "scoring_revision": { - "kind": "webmainbench_teds_tree_adapter_correction", - "receipt_sha256": "b2e1aa9218b8fe43cbb18f6ebefd8d606716009d978d897ada10255c6ec31024" - }, - "official_metrics": [ - "overall", - "text_edit", - "code_edit", - "formula_edit", - "table_edit", - "table_TEDS" - ], - "metric_config": { - "use_llm": false, - "reason": "deterministic official baseline mode; no model-dependent formula refinement" - }, - "input_contract": { - "body": "official html field encoded as UTF-8 without mutation", - "execution": "inline scripts may run; non-loopback subresources and data access are blocked; local engines receive the same body from an OS-isolated loopback HTTPS fixture with a per-run CA, Kitesurf receives the same body through Cloudflare Browser Rendering html input with external resource types rejected", - "original_url": "retained as evidence metadata only" - }, - "publication_state": "verified", - "unverified_reference_cases": [], - "require_independent_result_review": true, - "conversion": "Native Markdown for Kitesurf, Lightpanda, Obscura and Moli; Chromium whole-DOM HTML via node-html-markdown 2.0.0", - "artifact_hub_base_url": "https://artifact-site.lexmount.cn", - "artifact_hub_site": "uTWj6VaWPYCh", - "artifact_hub_expected_version": "ver_a9336a40214346d7bc541013e449045f", - "latency_reporting": { - "primary": "final_successful_fetch", - "time_field": "elapsed_ms", - "eligibility": "final_call_ok_and_nonempty_target_content_not_error_page", - "retry_time": "final_attempt_only", - "population": "each_method_successful_pages_with_explicit_coverage", - "matched_population": "all_method_success_intersection_diagnostic_only_no_ranking", - "retained_execution_cost": "total_elapsed_ms_all_200_pages" - }, - "latency_diagnostic": { - "kind": "moli_115_117_latency_rerun_v1", - "manifest_sha256": "2f3a544b7dd454791037c74954e8f50259ba1b2438356550a5021cf9811a6382", - "role": "append_only_latency_diagnostic_not_score_bearing" - } -} diff --git a/evaluation/lexbench-webfetch.json b/evaluation/lexbench-webfetch.json index fcd844a55..56f98bcff 100644 --- a/evaluation/lexbench-webfetch.json +++ b/evaluation/lexbench-webfetch.json @@ -2,7 +2,7 @@ "schema_version": 1, "kind": "external_benchmark_binding", "repository": "https://github.com/lexmount/lexbench-webfetch", - "revision": "5ac291a592fec849d86bbb791a80579cc7a6c2c6", + "revision": "6bd263ed98030a883e66ac75a47ac4e7fbae4ad1", "ownership": { "benchmark": "Task populations, task model, acceptance scoring, file checks, compiler and frozen definitions", "harness": "Execution, reference lifecycle, evidence and reports" @@ -78,7 +78,8 @@ "evaluation/risk-control/remaining-70-rule-review.md": "301574dddcfddf6d30ed4d882cdd40b1ae859fdc95a0a02cf9868f55e3fd1079", "evaluation/risk-control/rule-authoring-guide.md": "74363ab502978179e5438a0b992bac7563090e41c91784e492636832dc1cd088", "evaluation/risk-control/source-inputs.json": "77c5a4b62c07d59ed57154a833e85f9e7a97bc739730f51cd4ca3a698edb9258", - "evaluation/scoring-regressions.json": "59635a2b9b73681fd12b401f56e532cbaaefe11c891f1bb059da1435b133b02b", - "evaluation/source-inputs.json": "c202abc4624811b938a837402bbaf0ecbe716e375fbca70567346d2edb3b1708" + "evaluation/scoring-regressions.json": "432ddabe23d045d0ce4c23aef69ca3337ba4d240fe3c2061932cee26b7dc00dc", + "evaluation/source-inputs.json": "c202abc4624811b938a837402bbaf0ecbe716e375fbca70567346d2edb3b1708", + "annotation-revisions/serialization-20261021.json": "e0fe42179e584abb73782cbc459f521cab9cb09d3442de1ec4a6ff54d1b1235a" } } diff --git a/evaluation/live-webfetch/current-report.json b/evaluation/live-webfetch/current-report.json deleted file mode 100644 index 19ccacf3a..000000000 --- a/evaluation/live-webfetch/current-report.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "schema_version": 1, - "track": "webfetch", - "variant": "diagnostic-cohorts", - "evidence_state": "diagnostic", - "certified": false, - "diagnostic_input": { - "path": "latest-report-input.json", - "sha256": "1107f0d6f3f7fcbebf22522565adf99d567b63909c78c677772eb14f6d53ba5f" - }, - "cohort_plan": [ - {"id": "popular", "case_count": 100, "case_ids_sha256": "c161b71632bf38defd71e95c50eb557ca3c3747a7ed457b5ea93b288f0e46a31"}, - {"id": "dynamic", "case_count": 100, "case_ids_sha256": "d317fc151528bd93e02d4a0b2ae08f3a62bda502a99c9812eecace71a3b20269"}, - {"id": "risk", "case_count": 100, "case_ids_sha256": "c2ffa9b81ab8ae2aecd8a5fab568803616dd49713d21cfa9419e5ee32e373129"} - ], - "implementation": { - "reported_version": "moli 1.1.15", - "binary_sha256": "7a53cdd071ae60a2f8ff6d08107f67f6b0f1d6247a9acd970d2bcec77ff15b50" - }, - "model_sha256": "d7b9bbfb91748579f48c4cfd4e51ea05f5e0c92b17638038eb1ab1a0894e2698", - "report_output": "reports/live-webfetch", - "publication": { - "host": "https://artifact-site.lexmount.cn", - "slug": "5f1BlXJW61lr", - "version_id": "ver_790d96caaf824094a3f206f01dd56cd1", - "share_created": false, - "verified_files": 1815, - "export_archive_sha256": "edd6ba88f7a1602e4da7729cc2f6f10c95e818aa253a94fdee9d4f7966fe6be6" - } -} diff --git a/evaluation/moli/current-report.json b/evaluation/moli/current-report.json deleted file mode 100644 index e02761f17..000000000 --- a/evaluation/moli/current-report.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "schema": "moli_report_profile/1", - "functional_population": { - "cases": 372, - "attempts_per_case": 3, - "rule": "all_three_attempts_pass" - }, - "resource_population": { - "tasks": 557, - "attempts_per_task": 5, - "chart_tasks": 209, - "chart_calls": 1045 - }, - "method_ids": [ - "114_off", - "118_off", - "118_on", - "119_official_off", - "119_official_on", - "119_off", - "119_on" - ], - "resource_method_ids": [ - "119_official_off", - "119_official_on", - "119_off", - "119_on" - ], - "publication": { - "base_url": "https://artifact-site.lexmount.cn", - "slug": "gdwtSGOel0va", - "kind": "folder", - "expected_version": "ver_460f324bc0c04d53a866517db54edd13", - "share_url": "https://artifact-site.lexmount.cn/v/w1vgSkFJjvmCL6kPaHmj4K8sUZ8iTI8t" - }, - "resource_builds": { - "119_official_off": { - "sha256": "4a10cfab1d743ff327d6e1d5e1a7a8702bed5d869544089ef7df231b0e3b453a", - "source_commit": "47512623315aa9b57edc2ef721a033b6b8d91063", - "profile": "release", - "target": "aarch64-unknown-linux-gnu" - }, - "119_official_on": { - "sha256": "4a10cfab1d743ff327d6e1d5e1a7a8702bed5d869544089ef7df231b0e3b453a", - "source_commit": "47512623315aa9b57edc2ef721a033b6b8d91063", - "profile": "release", - "target": "aarch64-unknown-linux-gnu" - }, - "119_off": { - "sha256": "568874d3f943957e003e6c7ca34163372c1d3df8823431377037dfae86d99cbd", - "source_commit": "f315ff26ed6222603d382b8f7a4648157da3ebf9", - "profile": "release", - "target": "aarch64-unknown-linux-gnu" - }, - "119_on": { - "sha256": "568874d3f943957e003e6c7ca34163372c1d3df8823431377037dfae86d99cbd", - "source_commit": "f315ff26ed6222603d382b8f7a4648157da3ebf9", - "profile": "release", - "target": "aarch64-unknown-linux-gnu" - } - } -} diff --git a/evaluation/popular-sites/current-report.json b/evaluation/popular-sites/current-report.json deleted file mode 100644 index 86cce7f46..000000000 --- a/evaluation/popular-sites/current-report.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "schema_version": 1, - "dataset_id": "popular-sites", - "comparison_kind": "popular_sites", - "case_count": 100, - "case_ids_sha256": "9942d352ddc025e4a3d73b946722146bfa1f5b6569cccd5680ad91ef21ba3ccd", - "providers": [ - "moli-webfetch" - ], - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "reference_binding_mode": "hash_bound_stable_text_and_structure", - "publication_state": "measured_pending_report_review", - "unverified_reference_cases": [], - "require_independent_result_review": true, - "conversion": "Offline scoring of preserved Moli 1.1.5 outputs against the current canonical 100-site rules. Requests, outputs, logs and latency are unchanged; no provider calls.", - "report_output": "reports/popular-sites", - "artifact_site_base_url": "https://artifact-site.lexmount.cn" -} diff --git a/evaluation/trajectory-replay/webarena-verified-full/current-comparison.json b/evaluation/trajectory-replay/webarena-verified-full/current-comparison.json deleted file mode 100644 index 3641c8b22..000000000 --- a/evaluation/trajectory-replay/webarena-verified-full/current-comparison.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "schema": "lex_webarena_replay_comparison_release/1", - "benchmark": "WebArena-Verified", - "split": "full-812", - "evidence_root": "trajectory-replay/full-812-five-browsers-six-methods-20260929", - "execution_backend": "playwright", - "resource_collection_status": "not_recorded", - "task_count": 812, - "logical_results": 4872, - "physical_executions": 6554, - "participants": [ - { - "id": "chromium", - "engine": "chromium", - "label": "Chromium 153", - "pass": 812, - "fail": 0 - }, - { - "id": "moli-pr731-pr853", - "engine": "moli", - "label": "Moli · #731 + #853", - "pass": 812, - "fail": 0 - }, - { - "id": "moli-v1.1.11", - "engine": "moli", - "label": "Moli v1.1.11", - "pass": 624, - "fail": 188 - }, - { - "id": "lightpanda-nightly-9813", - "engine": "lightpanda", - "label": "Lightpanda nightly 9813", - "pass": 330, - "fail": 482 - }, - { - "id": "obscura-0.2.3", - "engine": "obscura", - "label": "Obscura 0.2.3", - "pass": 386, - "fail": 426 - }, - { - "id": "kitesurf-cloudflare", - "engine": "kitesurf", - "label": "Kitesurf · Cloudflare Browser Run", - "pass": 441, - "fail": 371 - } - ], - "artifacts": { - "profile": { - "path": "profile.json", - "sha256": "d9fe439d7800bba3e8b62577631bc682b53b933a27d8244aafc0db0330faa47f" - }, - "dataset": { - "path": "dataset.json", - "sha256": "ce40a54d399c3aa33487621e8d7e05aab293fc2547edf0786d5fdbd699216e74" - }, - "results": { - "path": "results.json", - "sha256": "6074435e6a16976fb77c2bac48ea8a580e11d6d70b779b6fef69a9b082f82b62" - }, - "failure_review": { - "path": "failure-review.json", - "sha256": "27a15133cf2910c3a1ebfecc684a6ada851a63aed7604328857e7286b7d08ff2" - }, - "execution_metadata_revision": { - "path": "execution-metadata-revision.json", - "sha256": "95e19532a4d5e44399fc06656273303a6fc70c20e4b404ba74fdc81d29892411" - }, - "report_manifest": { - "path": "report/report-manifest.json", - "sha256": "6fb52c5c78e87863a40b8ba8189e860740e3cb63b5780670fc85b3acaac56ad8" - }, - "publication_archive": { - "path": "artifact-site-package.zip", - "sha256": "db50211d6f03c91d25b57911637ed19f52b2a5bbb3fb568db9dc0ec31d5f1fd9" - }, - "publication_attestation": { - "path": "artifact-site-attestation.json", - "sha256": "9fddc99a0ace95addde143730dea77564377aaf3bd1c8cab2bdb627623d75b56" - }, - "publication_receipt": { - "path": "artifact-site-receipt.json", - "sha256": "a9e5fdf7fab0ce544957576eb06d6a48fd0689730ef1bf4ddaac22779cd5581a" - } - }, - "publication": { - "service": "artifact-site", - "host": "https://artifact-site.lexmount.cn", - "slug": "EFLrNgujgsVc", - "version_id": "ver_c0f0693e9c08401bb4d2b88393234e55", - "share_url": "https://artifact-site.lexmount.cn/v/4G8YKfQreobGLwg4fo-UJGohezNqgAEm" - } -} diff --git a/evaluation/trajectory-replay/webarena-verified-full/current-report.json b/evaluation/trajectory-replay/webarena-verified-full/current-report.json deleted file mode 100644 index 8db04cca8..000000000 --- a/evaluation/trajectory-replay/webarena-verified-full/current-report.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "schema": "lex_webarena_verified_full_report_profile/1", - "benchmark": "WebArena-Verified", - "split": "full-812", - "browser": "moli", - "dataset_path": "evaluation/trajectory-replay/webarena-verified-full/dataset.json", - "dataset_sha256": "74fd5a77dc652216628e2a4ff7b0b249045f10b8cc8a21dc710d3d05a591dde1", - "scope_path": "evaluation/trajectory-replay/webarena-verified-full/scope.json", - "scope_sha256": "a209bc38d06e51dab3b32269be88ff5e9c30b8ccfebbac39ef56e20da4b994df", - "evidence_root": "trajectory-replay/full-812-playwright-moli-20260926", - "accepted_catalog": { - "path": "accepted-catalog-final.json", - "sha256": "7a4d780f35eec75c9fac75666dc75f25b9fb33aeabe754bda55857ed286702e0" - }, - "accepted_portable_suite": { - "path": "accepted-suite-final.json", - "sha256": "7bbcf099ce13d9b0bce9652a32f24df8aef489dcaf3dac2702daed33decd062e" - }, - "acceptance_ledger_path": "acceptance-final-v20.json", - "acceptance_ledger_sha256": "83d4ff9ca2291d48647d453f85cf3c0c7d433731548ca694506f377831b0a46c", - "accepted_counts": { - "total": 812, - "pass": 812, - "unresolved": 0, - "physical_attempts": 1024, - "excluded_duplicate_attempts": 0 - }, - "report_manifest_path": "report-moli-final-v1/manifest.json", - "report_manifest_sha256": "b0213c2f31d419a8aa7ad6e5a11b8576727ab9aa06458177fb0d3bbe85472a5a", - "report_archive_path": "report-moli-final-v1.zip", - "report_archive_sha256": "d43188f4c9c54b9d353acdac481eb680178814393f53a4f699206c5604da955a", - "generated_languages": [ - "zh" - ], - "publication": { - "service": "artifact-site", - "host": "https://artifact-site.lexmount.cn", - "title": "WebArena-Verified 全量轨迹重放 · Moli", - "slug": "EFLrNgujgsVc", - "version_id": "ver_e65cd20b5a784e2d84381bf00e3df02e", - "share_url": "https://artifact-site.lexmount.cn/v/4G8YKfQreobGLwg4fo-UJGohezNqgAEm" - }, - "execution_backend": "playwright" -} diff --git a/evaluation/trajectory-replay/webarena-verified/current-run.json b/evaluation/trajectory-replay/webarena-verified/current-run.json deleted file mode 100644 index 80abbbab5..000000000 --- a/evaluation/trajectory-replay/webarena-verified/current-run.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "schema": "lex_webarena_trajectory_replay_profile/1", - "benchmark": "WebArena-Verified", - "measurement_notes": { - "zh": "原三组结果保留,两组 1.1.9 方法为后续追加;新 PR 使用独立重编译的 release 构建。各版本没有在同一时间窗口内逐题交替运行,且 1.1.9 正式版开头存在本机编译和其他任务并行。耗时统计描述实际调用,不用于推断源码提速。 第 448、510 题的新两组分别恢复最后一次保存检查和商品数量检查;旧三组保留历史验收与成绩。这两题的规则版本在任务详情中分别列出,不作为纯浏览器差异。", - "en": "The original three cohorts are retained and two 1.1.9 methods are appended. The new PR uses a separately rebuilt release binary. Builds were not alternated per task in one time window, and the initial official 1.1.9 calls overlapped local compilation and other work. Timings describe observed calls, not code-induced speedup. For tasks 448 and 510, the appended methods restore final-save and item-quantity checks respectively. Original cohorts retain historical acceptance and scores. Task pages list both rule versions; these two tasks are not pure browser comparisons." - }, - "benchmark_version": "1.2.3", - "task_selection": { - "path": "evaluation/trajectory-replay/webarena-verified/selection.json", - "sha256": "06d8d999164c7c2de6cb21d15ec12f5260bfc2a58ca81b098a8c7779d4d2d91a", - "task_count": 90 - }, - "seed_inventory": { - "base_kind": "successful Browser Use executions", - "expert_repairs_path": "evaluation/trajectory-replay/webarena-verified/expert-repairs.json", - "expert_repairs_sha256": "4eb4692ff8aefc39d3c7345e21f819fd9248a9b6ebe2e70c1a4c878a2d911a02", - "required_ready_tasks": 90, - "canonical_trajectories_per_task": 1 - }, - "dataset": { - "path": "evaluation/trajectory-replay/webarena-verified/trajectories-corrected.json", - "sha256": "624a0ea83c6e4c3533714e961656d7e4159189a9e4941017b06a5299e4dc31d5" - }, - "methods": [ - { - "id": "chromium", - "engine": "chromium", - "label": "Chromium", - "binary_sha256": "d999cf00c6244a84279785c48eb78f97764a8a13ea3a1a7cc5030bf2f4ca481e" - }, - { - "id": "moli-1.1.8", - "engine": "moli", - "label": "Moli 1.1.8", - "binary_sha256": "522f752f30397f3b77910736c4ff76f2bdf4cd1a62a2bd14332bbccc97ce26dc" - }, - { - "id": "moli-1.1.8-pr-39d75b69", - "engine": "moli", - "label": "Moli 1.1.8 · PR 640 (39d75b69)", - "binary_sha256": "1c4dbbe67e4b97c903b3916fe65f8c5676c21810a1f4a9095cf6dfbd9d9286d4" - }, - { - "id": "moli-1.1.9", - "engine": "moli", - "label": "Moli 1.1.9", - "binary_sha256": "79699e50476118a7a5d1a36775f52d4106801fd5bc803727fd317501faa840cd" - }, - { - "id": "moli-1.1.9-pr-f315ff26", - "engine": "moli", - "label": "Moli 1.1.9 · PR 640 (f315ff26)", - "binary_sha256": "9b3f3dd6e550a71cddfb93ec3490317b0dfa5db407ebc2d5fb0a9748f58dd226" - } - ], - "qualification": { - "reference_engine": "chromium", - "required_task_count": 90 - }, - "runs_per_task": 1, - "resources": { - "browser_host": "macOS arm64 host", - "browser_cpu_limit": null, - "browser_memory_limit": null, - "sample_interval_ms": 100 - }, - "execution": { - "environment_pool_count": 1, - "method_parallelism": 1, - "task_parallelism": 1, - "schedule": "complete one method before starting the next", - "reset_before_every_task": false, - "reset_before_every_method": true, - "teardown_between_methods": true, - "retry_failed_task": false, - "trusted_source_site_hosts": [ - "192.168.5.2", - "host.docker.internal" - ], - "site_stack": "one self-hosted Rainbond WebArena app slice, restored once before each browser method" - }, - "scoring": { - "authority": "official programmatic evaluator or a hash-bound replay correction", - "require_all_reference_validity": true, - "promotion_gate": "the canonical trajectory passes execution and official evaluation on the frozen reference browser before any measured browser comparison", - "model_calls": 0 - }, - "publication": { - "base_url": "https://artifact-site.lexmount.cn", - "title": "浏览器轨迹重放:Chromium 与 Moli 五种方法对比(90 题)", - "site_slug": "KoYm0UCog5th", - "share_url": "https://artifact-site.lexmount.cn/v/coY1pMVPbD6Z8cVLaUi_ZbpSnalfKpxY", - "expected_version": "ver_a9f677cf6bd148a9b8042867b861de19", - "state": "verified" - }, - "acceptance_revisions": { - "448": { - "chromium": { - "correction_id": "wv-448-profile-save-success-variants-20260918", - "corrected_task_sha256": "f6330b0eef45ab950be13b2db63f51ee3eab99560ab2527cde22131440959f10" - }, - "moli-1.1.8-pr-39d75b69": { - "correction_id": "wv-448-profile-save-success-variants-20260918", - "corrected_task_sha256": "f6330b0eef45ab950be13b2db63f51ee3eab99560ab2527cde22131440959f10" - }, - "moli-1.1.9-pr-f315ff26": { - "correction_id": "wv-448-final-profile-save-success-variants-20260920", - "corrected_task_sha256": "6543e072d795f6446d2393d1e79a6075c2e06e600c73c418acb931dad3cc4f9e" - } - }, - "510": { - "chromium": { - "correction_id": "wv-510-freeze-target-and-use-cart-dom-20260912", - "corrected_task_sha256": "8b59be6dc204f64a6a3d92aff6f780fb91508894c9b75586d2fa79202e2c86cd" - }, - "moli-1.1.8": { - "correction_id": "wv-510-freeze-target-and-use-cart-dom-20260912", - "corrected_task_sha256": "8b59be6dc204f64a6a3d92aff6f780fb91508894c9b75586d2fa79202e2c86cd" - }, - "moli-1.1.8-pr-39d75b69": { - "correction_id": "wv-510-freeze-target-and-use-cart-dom-20260912", - "corrected_task_sha256": "8b59be6dc204f64a6a3d92aff6f780fb91508894c9b75586d2fa79202e2c86cd" - }, - "moli-1.1.9": { - "correction_id": "wv-510-restore-cart-quantity-contract-20260920", - "corrected_task_sha256": "f94a3014dbda8cd6d5ffafe5816a3f249df6a9ea7b00a027c9e2710eb0af2a96" - }, - "moli-1.1.9-pr-f315ff26": { - "correction_id": "wv-510-restore-cart-quantity-contract-20260920", - "corrected_task_sha256": "f94a3014dbda8cd6d5ffafe5816a3f249df6a9ea7b00a027c9e2710eb0af2a96" - } - } - } -} diff --git a/evaluation/wcxb/current-report.json b/evaluation/wcxb/current-report.json deleted file mode 100644 index 222ec0326..000000000 --- a/evaluation/wcxb/current-report.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "schema_version": 1, - "report_assets": { - "content/wcxb-repair-zh.fragment": "b2533e55598c28a153d2286ee3c3b1a23ca100db1702a0a657cc49548aa0fad6", - "content/wcxb-repair-en.fragment": "f41560e6ec2b1d2e15b2bfb3af588ee41ec623a5ff6e24c080e2051ff012c479", - "assets/wcxb-repair/adjacent-code.png": "fdcf8020a8e79dda7fdc5039f9d9a6a3c3cf7dee10914993ca528fa60422bfab", - "assets/wcxb-repair/code-block.png": "67cc6b592f793b0079187042d4f347d8302253082eb9f1e42788bafe149597f2", - "assets/wcxb-repair/description.png": "efc0a24c2c37941c25a5bf1886067cd0a96f6da535b880cc8b64ffbacc461834", - "research/wcxb-representative-review.json": "1825cd79e678323967606d5b23326d248038c9f3c36409c17a1622d0adf0a13f" - }, - "dataset_id": "wcxb-v1-test-qualified-five-browsers", - "comparison_kind": "wcxb_frozen_html_to_markdown", - "case_count": 504, - "primary_quality_case_count": 504, - "split": "test", - "sample_manifest": "evaluation/public-wcxb-test-qualified.json", - "sample_manifest_sha256": "2f42daa5a793cdf64654bcc1e58e332315c0d972bbc9e2e06510cfa59ea94754", - "source_repository": "https://github.com/Murrough-Foley/web-content-extraction-benchmark", - "source_commit": "c039d5ee9f5a3a984a0e167e63aacd04e76e78a9", - "source_dataset_sha256": "080a35a399880bb74ce23f40d9a1008f974500716af183a990f8701ab3e5e7bb", - "official_evaluator_sha256": "ae4ad6299e190177fbb04a7c1190077fc6086b3fa0f31fb37a6538a5e979c559", - "providers": [ - "kitesurf", - "lightpanda", - "obscura", - "moli", - "chromium", - "moli-pr643" - ], - "versions": { - "kitesurf": "Cloudflare hosted stable backend; version undisclosed", - "lightpanda": "0.4.1", - "obscura": "0.2.2", - "moli": "1.1.8", - "chromium": "Google Chrome for Testing 153.0.8010.47", - "moli-pr643": "1.1.7 source build, PR #643, commit 384d1d2a930c86a7b3421d839a8efc588a6bb0b4" - }, - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "max_failure_retries": 1, - "reference_binding_mode": "pinned_wcxb_v1_test_html_and_ground_truth_pre_and_post_verified", - "official_metrics": [ - "precision", - "recall", - "f1", - "with_rate", - "without_rate" - ], - "output_contract": "Markdown from the complete frozen page; no common main-content selector", - "publication_state": "published", - "require_independent_result_review": false, - "artifact_hub_base_url": "https://artifact-site.lexmount.cn", - "artifact_hub_site": "p3sFcZrww_nx", - "artifact_hub_expected_version": "ver_8ea488f5197c4b57a94c0e7987587e26", - "artifact_hub_share_url": "https://artifact-site.lexmount.cn/v/7GfS87N6u4G1VWcEJlT9sG3j8YWV1Zjo", - "candidate_source_commit": "384d1d2a930c86a7b3421d839a8efc588a6bb0b4", - "candidate_binary_sha256": "ba5e80975886f3135098146acec6941cee6d4bc65109d2e576db9dfc1e1c0ade", - "release_binary_sha256": "522f752f30397f3b77910736c4ff76f2bdf4cd1a62a2bd14332bbccc97ce26dc", - "latency_correction_receipt_sha256": "701799a5af8fea1ad02f4f4650c695390d2c6571eb69bda115392160e4e34681", - "optional_ui_receipt_sha256": "6bb78355ec3343c532023dbc0ba809f41554e74874932b6f95581b87f25d7a81" -} diff --git a/evaluation/wcxb/report-content-binding.json b/evaluation/wcxb/report-content-binding.json deleted file mode 100644 index 9b339e1ff..000000000 --- a/evaluation/wcxb/report-content-binding.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "schema_version": 1, - "relative_path": "report-content.json", - "sha256": "bc1bccd2521821af9d91bf997b3bdbf2370ce25cda32756f1897ef26561cbf0a", - "evidence": { - "wcxb-score-summary.json": "0339e3509153e7e4ffaf4341acc944aeb481c29124e6ab2bbf9b964b21c4a1a1", - "wcxb-scores.jsonl": "d544a7a0cd9753e84339647f246575f53a0db355a9f147f9d155191b5ed914dd", - "certification.json": "870b249b75300e4c5572d458eab3b65cd5353bae355d40c2722462ad0245d2ef", - "latency-correction/receipt.json": "701799a5af8fea1ad02f4f4650c695390d2c6571eb69bda115392160e4e34681", - "optional-ui-4017/receipt.json": "6bb78355ec3343c532023dbc0ba809f41554e74874932b6f95581b87f25d7a81", - "report-assets/research/wcxb-representative-review.json": "1825cd79e678323967606d5b23326d248038c9f3c36409c17a1622d0adf0a13f" - }, - "authority": { - "evaluation/public-wcxb-test-qualified.json": "2f42daa5a793cdf64654bcc1e58e332315c0d972bbc9e2e06510cfa59ea94754" - }, - "source": { - "url": "https://artifact-site.lexmount.cn/s/p3sFcZrww_nx?version=ver_8ea488f5197c4b57a94c0e7987587e26", - "version": "ver_8ea488f5197c4b57a94c0e7987587e26", - "profile_sha256_at_restoration": "7437f8b789574ce73c4f801044a35f2ceda04c475020b02e69a66b6c746acb5b", - "recovery": { - "kind": "html-main-json-v1", - "container": "files", - "fields": { - "guide": { - "zh": "guide/index.html", - "en": "en/guide/index.html" - } - } - }, - "files_sha256": { - "guide/index.html": "d628f16915235884e206cdb7ed27b86fde686b7b473d3ce2ba236dc6f816d0c6", - "en/guide/index.html": "e275f142073dfb847bb24bbdb9f9f1ea3e096852266273c3ffb5d403ef2141d8" - } - }, - "rendered_sha256": { - "guide": { - "zh": "da74f550612f5564494a4c6535849347204faad853844b439b27ea8352c50235", - "en": "681da8a72ac20a5b5cf4daaf0933302933ed6528f8b805473753def2d9d16ab5" - } - } -} diff --git a/evaluation/webmainbench-full/case-correction-binding.json b/evaluation/webmainbench-full/case-correction-binding.json deleted file mode 100644 index cbd66fec9..000000000 --- a/evaluation/webmainbench-full/case-correction-binding.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "schema_version": 1, - "relative_path": "evidence/webmainbench-545-origin-correction-seven-pr919-721bd2e-authority-rescored-20261008", - "sha256": "72be654df28c75eeca879101b0eb416dbcbe3b8e2f79e1b5073e9f8ee9ea52f2" -} diff --git a/evaluation/webmainbench-full/content-binding.json b/evaluation/webmainbench-full/content-binding.json deleted file mode 100644 index 3166b6dc4..000000000 --- a/evaluation/webmainbench-full/content-binding.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "schema_version": 1, - "relative_path": "seven-method-moli-repairs/report-content.json", - "sha256": "9c6576a4991ba7ef319a9941c2bed13be1b76c487cdd5b82a1b2445c2394fd57" -} diff --git a/evaluation/webmainbench-full/current-report.json b/evaluation/webmainbench-full/current-report.json deleted file mode 100644 index 457b83388..000000000 --- a/evaluation/webmainbench-full/current-report.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schema_version": 1, - "dataset_id": "webmainbench-545-full-five-methods", - "comparison_kind": "webmainbench_frozen_html_to_markdown", - "case_count": 545, - "case_ids_sha256": "5c0cf5810602b70e8f7c1e16723e24a49c3ea207d54865df5c03c38f3d88ffd5", - "source_dataset_sha256": "0efaa4b49a45e320a27fe6e5a0b6aad5b57259fc3321ac3448519cacc74c537e", - "providers": ["moli", "lightpanda", "obscura", "chromium", "kitesurf"], - "versions": { - "kitesurf": "Cloudflare hosted stable backend; version undisclosed", - "lightpanda": "0.4.1", - "obscura": "0.2.2", - "moli": "1.1.9", - "chromium": "Google Chrome for Testing 154.0.8037.57" - }, - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "max_failure_retries": 1, - "reference_binding_mode": "complete_webmainbench_545_official_frozen_html_and_groundtruth_content", - "official_scorer_commit": "9d991bdc00c57b57521499494d96be85c31317ba", - "metric_config": {"use_llm": false}, - "official_metrics": ["overall", "text_edit", "code_edit", "formula_edit", "table_edit", "table_TEDS"], - "input_contract": { - "body": "official html field encoded as UTF-8 without mutation", - "execution": "inline scripts may run; non-loopback subresources and data access are blocked; local engines receive the same body from an OS-isolated loopback HTTPS fixture with a per-run CA, Kitesurf receives the same body through Cloudflare Browser Rendering html input with external resource types rejected", - "original_url": "retained as evidence metadata only" - }, - "publication_state": "pending", - "require_independent_result_review": false, - "conversion": "Native Markdown for Kitesurf, Lightpanda, Obscura and Moli; Chromium whole-DOM HTML via node-html-markdown 2.0.0" -} diff --git a/evaluation/webmainbench-full/failure-analysis-binding.json b/evaluation/webmainbench-full/failure-analysis-binding.json deleted file mode 100644 index d31082508..000000000 --- a/evaluation/webmainbench-full/failure-analysis-binding.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "schema_version": 1, - "relative_path": "failure-analysis.json", - "sha256": "b04c476bf39b094260a869fbc42917fa62ad0e3fc3738ac53dcd70235b51bbbc" -} diff --git a/evaluation/webmainbench-full/moli-content-fixed-report.json b/evaluation/webmainbench-full/moli-content-fixed-report.json deleted file mode 100644 index b00e4841a..000000000 --- a/evaluation/webmainbench-full/moli-content-fixed-report.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "schema_version": 1, - "dataset_id": "webmainbench-545-moli-fixed", - "comparison_kind": "webmainbench_frozen_html_to_markdown", - "case_count": 545, - "case_ids_sha256": "5c0cf5810602b70e8f7c1e16723e24a49c3ea207d54865df5c03c38f3d88ffd5", - "source_dataset_sha256": "03bc6370a2b37427eba94f95268cf3c935ac89b15b184dfc47f84b78b97fe18e", - "providers": [ - "moli" - ], - "versions": { - "moli": "1.1.11" - }, - "binary_sha256": "fac987f54939fab2f5db7bf54af41b25cbb3aed3230528c170b4c22ada0b98ac", - "source_commit": "721bd2e329f96c06573db6e6b257a149cf17a6fe", - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "max_failure_retries": 1, - "reference_binding_mode": "complete_webmainbench_545_official_frozen_html_and_groundtruth_content", - "official_scorer_commit": "f7cb97df2dfb5ac10ebc97cd0b8f9d73ab40f50b", - "metric_config": { - "use_llm": false - }, - "official_metrics": [ - "overall", - "text_edit", - "code_edit", - "formula_edit", - "table_edit", - "table_TEDS" - ], - "input_contract": { - "body": "official html field encoded as UTF-8 without mutation", - "execution": "inline scripts may run; non-loopback subresources and data access are blocked; local engine receives the same body from an OS-isolated loopback HTTPS fixture with a per-run CA", - "original_url": "retained as evidence metadata only" - }, - "publication_state": "pending", - "require_independent_result_review": false, - "conversion": "Native Markdown from Moli PR #919 after shared fallback semantics, live form state, proven interactive disclosures, resolved cascade visibility, and composed final animation effects, based on main cb56b3b33" -} diff --git a/evaluation/webmainbench-full/moli-pr785-fixed-report.json b/evaluation/webmainbench-full/moli-pr785-fixed-report.json deleted file mode 100644 index d3a31e445..000000000 --- a/evaluation/webmainbench-full/moli-pr785-fixed-report.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "schema_version": 1, - "dataset_id": "webmainbench-545-moli-fixed", - "comparison_kind": "webmainbench_frozen_html_to_markdown", - "case_count": 545, - "case_ids_sha256": "5c0cf5810602b70e8f7c1e16723e24a49c3ea207d54865df5c03c38f3d88ffd5", - "source_dataset_sha256": "0efaa4b49a45e320a27fe6e5a0b6aad5b57259fc3321ac3448519cacc74c537e", - "providers": ["moli"], - "versions": {"moli": "1.1.9"}, - "binary_sha256": "48f45a608ce41d3c6600ca88df964e580aecf353289e028e8d9f7703224ab3b1", - "source_commit": "a50182cbe4eef20b230d3f01e452d9de4f3146ba", - "timeout_seconds": 45, - "runs_per_case": 1, - "concurrency": 1, - "max_failure_retries": 1, - "reference_binding_mode": "complete_webmainbench_545_official_frozen_html_and_groundtruth_content", - "official_scorer_commit": "9d991bdc00c57b57521499494d96be85c31317ba", - "metric_config": {"use_llm": false}, - "official_metrics": ["overall", "text_edit", "code_edit", "formula_edit", "table_edit", "table_TEDS"], - "input_contract": { - "body": "official html field encoded as UTF-8 without mutation", - "execution": "inline scripts may run; non-loopback subresources and data access are blocked; local engine receives the same body from an OS-isolated loopback HTTPS fixture with a per-run CA", - "original_url": "retained as evidence metadata only" - }, - "publication_state": "pending", - "require_independent_result_review": false, - "conversion": "Native Markdown from Moli fixed build" -} diff --git a/evaluation/webmainbench-full/moli-variants.json b/evaluation/webmainbench-full/moli-variants.json deleted file mode 100644 index 6f1fe9030..000000000 --- a/evaluation/webmainbench-full/moli-variants.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "schema_version": 1, - "variants": [ - { - "provider": "moli-pr785-fixed", - "profile": "moli-pr785-fixed-report.json", - "evidence_alias": "moli-fixed", - "label": "Moli 1.1.9 · PR785 修复版", - "short_label": "PR785 修复版", - "role": "prior_repair", - "pull_request": "https://github.com/lexmount/moli/pull/785" - }, - { - "provider": "moli-content-fixed", - "profile": "moli-content-fixed-report.json", - "evidence_alias": "moli-fixed", - "label": "Moli 1.1.11 · PR919 721bd2e 实测版", - "short_label": "PR919 修复版", - "role": "content_repair", - "pull_request": "https://github.com/lexmount/moli/pull/919" - } - ] -} diff --git a/evaluation/webmainbench-full/publication-target.json b/evaluation/webmainbench-full/publication-target.json deleted file mode 100644 index b710142f0..000000000 --- a/evaluation/webmainbench-full/publication-target.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schema_version": 1, - "profile_sha256": "8cbceaf9694e88e7c1ddbba3263573dcccf69b1ac89d7e9ea5aa098f2a3bab02", - "variant_profile_sha256": { - "moli-pr785-fixed": "db8e7fa408edf3dc2e19d2b0f35645448d0e1efd3ac86926d0bcc9e1a3fb3848", - "moli-content-fixed": "ee4c91daa5221a0e44a5f5300b03e379a3875623a31c0e0baf59c723a5e093e3" - }, - "host": "https://artifact-site.lexmount.cn", - "site_slug": "zrqPjO672dgg", - "share_url": "https://artifact-site.lexmount.cn/v/Avz3qzo6EEujI0iln1LAzCUF93I47OEC", - "expected_remote_version": "ver_cdd1a9316d6c4d49a5dc4fee4646fba1" -} diff --git a/examples/trajectory_replay/public_webarena/README.md b/examples/trajectory_replay/public_webarena/README.md index a1dedfba3..7b5f4f060 100644 --- a/examples/trajectory_replay/public_webarena/README.md +++ b/examples/trajectory_replay/public_webarena/README.md @@ -48,8 +48,8 @@ the suite, qualification reasons, evaluator rules, source bindings, and a saniti publication summary. Use `verify_metrics.py` to audit raw measurements and `rebuild_report.py` to render from existing attempts; neither reruns browsers. -Publishing uses the site and expected version in -`evaluation/browser-use-public/current-report.json`. The package attestation binds +Publishing uses the site and expected version in the external report profile +selected with `--profile`. The package attestation binds that profile and the complete archive. Interrupted submissions retain a private operation journal and receipt; retrying the release resumes the same submission. The profile advances only after the exported version and every packaged file have diff --git a/harness/browser_use/assets/webarena-autonomous-evaluation-flow.png b/harness/browser_use/assets/webarena-autonomous-evaluation-flow.png index 72fb2a7dd..a548992cc 100644 Binary files a/harness/browser_use/assets/webarena-autonomous-evaluation-flow.png and b/harness/browser_use/assets/webarena-autonomous-evaluation-flow.png differ diff --git a/harness/browser_use/failure_cause_review.py b/harness/browser_use/failure_cause_review.py index 0649067a8..03e26cfc4 100644 --- a/harness/browser_use/failure_cause_review.py +++ b/harness/browser_use/failure_cause_review.py @@ -17,6 +17,20 @@ def sha256(path: Path) -> str | None: return hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None +def review_path(profile: dict[str, Any], *, authority_root: Path | None = None) -> Path: + """Resolve an explicitly bound external review or a repository-relative review.""" + from lexbench_webfetch.paths import contained_path + + from harness.web_fetch.safety import unlinked_path + value = (profile.get("failure_cause_review") or {}).get("source_path") + if value is None: + value = "research/browser-use-failure-cause-review.json" + path = Path(value) + if path.is_absolute(): + return unlinked_path(path) + return contained_path(authority_root or Path(__file__).parents[2], value, reject_symlinks=True) + + def load_failure_cause_review(path: Path = DEFAULT_REVIEW_PATH) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) if path.is_file() else {} diff --git a/harness/browser_use/report-translations.json b/harness/browser_use/report-translations.json new file mode 100644 index 000000000..fcaf50503 --- /dev/null +++ b/harness/browser_use/report-translations.json @@ -0,0 +1,1531 @@ +{ + "TEXT_0": { + "暂无核验记录。": "No review records.", + "这是事后诊断口径,不是官方新分数。它按任务统一剔除任一配置已证实受任务/evaluator、站点、共享环境或参考状态干扰的任务,保证各配置分母一致。": "This post-hoc diagnostic is separate from official scores. Tasks with verified evaluator, site, shared-environment or reference interference are excluded across all configurations, preserving a common denominator.", + "全部参评配置在同一张表和任务矩阵中比较;失败结果可打开诊断和证据。": "All configurations appear in one comparison and task matrix; failed results open their diagnosis and evidence.", + "全部配置中通过数最高": "Highest pass count across all configurations", + "全部配置中通过率最高": "Highest pass rate across all configurations", + "修正验收合同,通过独立预检后重跑全部参评配置。": "Correct the acceptance contract, complete independent preflight checks, then rerun every configuration.", + "OpenAI、Anthropic、Google DeepMind、THUDM / ChatGLM、Browser Use。原版 Mind2Web、Online-Mind2Web 与 Mind2Web 2 的任务和评分器不同,不能合并到同一成绩表。": "OpenAI, Anthropic, Google DeepMind, THUDM / ChatGLM and Browser Use. Original Mind2Web, Online-Mind2Web and Mind2Web 2 use different tasks and evaluators; their scores cannot be combined in one table.", + "修复日志": "Fix log", + "配置": "Configuration", + "保留原合同的任务仍有自主执行失败": "Autonomous failures remain under original contracts", + "全部原合同任务": "All original-contract tasks", + "历史成功路径来源": "Historical-success origins", + "专家修复路径来源": "Expert-repair origins", + "轨迹来源与验收合同": "Trajectory origins and acceptance contracts", + "轨迹来源": "Trajectory origin", + "官方原合同": "Original official contract", + "修订合同": "Revised contract", + "历史成功轨迹": "Historically successful trajectories", + "专家修复轨迹": "Expert-repaired trajectories", + "修订类别": "Revision category", + "验收差异": "Acceptance differences", + "仅修改事件选择": "Event selection only", + "按匹配条件选择保存请求,避免随后请求覆盖。": "Select the matching save request without allowing subsequent requests to replace it.", + "事件选择与预期字段修改": "Event selection and expected fields", + "参考答案或验收要求修改": "Reference answers or acceptance requirements", + "固定动作、预录回答与绑定合同的重放验收结果。": "Replay acceptance for fixed actions, prerecorded responses, and bound contracts.", + "操作轨迹": "Action trajectory", + "Moli 的同题证据": "Moli evidence for the same task", + "从失败结果追到决策与运行设置": "Tracing failures to decisions and runtime settings", + "Task 124 · Astra × Chromium": "Task 124 · Astra × Chromium", + "Task 510 · Sol × Moli": "Task 510 · Sol × Moli", + "固定轨迹控制:它验证浏览器执行,不测自主规划": "Fixed-trajectory control: it tests browser execution, not autonomous planning", + "轨迹重放把既定 BrowserTool 调用交给浏览器,模型调用数为 0;它证明 Chromium 能在逐题重置的环境执行这 90 条合格轨迹,并通过同一官方 evaluator。它不让 Agent 在当次运行中观察、检索、比较、选择或决定何时结束。": "Trajectory Replay sends predetermined BrowserTool calls to the browser with zero model calls. It establishes that Chromium can execute these 90 qualified trajectories in per-task reset environments and pass the same official evaluator; it does not let an agent observe, retrieve, compare, select, or decide when to stop in that run.", + "Chromium:90/90": "Chromium: 90/90", + "固定轨迹执行与官方验收的上界控制。": "An upper-bound control for fixed-trajectory execution and official acceptance.", + "Moli:54/90": "Moli: 54/90", + "路径来源:30 + 60": "Trajectory origin: 30 + 60", + "数据绑定:": "Data bindings:", + "跨轨迹控制摘要": "Cross-track control summary", + "36 个 Moli 固定轨迹失败的逐题复核": "Per-task review of 36 Moli fixed-trajectory failures", + "完整执行证据": "Complete execution evidence", + "按原始结构展示本次物理执行的脱敏回答、evaluator、事件流、轨迹、计量与公开画面索引。": "Shows the sanitized response, evaluator record, event stream, trajectory, measurements, and public-frame index for this physical execution in their original structure.", + "执行证据 JSON": "Execution evidence JSON", + "正在读取证据…": "Loading evidence…", + "在页面中查看完整执行证据": "View complete execution evidence in the page", + "打开原始 JSON": "Open raw JSON", + "25 比 25 不是同因平局:五个零分包含四类根因": "25 vs. 25 is not a same-cause tie: five zero scores contain four root-cause classes", + "二、五个零分的根因与修复边界": "2. Root causes and repair boundaries for the five zero scores", + "两个浏览器共享 CowAgent、模型、任务合同与 evaluator,但这不意味着共同零分必然来自共同原因。根因审计把“页面上出现了什么”继续追到产生错误的合同、策略或采集实现,并为每项给出必须通过的复测条件。": "The two browsers share CowAgent, the model, task contracts, and evaluator, but shared zero scores do not necessarily share a cause. The root-cause audit traces visible symptoms to the contract, policy, or collection implementation that produced them and defines a required retest gate for each.", + "根因": "Root cause", + "责任层": "Owning layer", + "证据闭环": "Evidence chain", + "根治措施": "Corrective action", + "复测门槛": "Retest gate", + "表 2|历史官方分数不被人工改写;合同或运行协议修复后必须产生新证据。": "Table 2 | Historical official scores are not manually changed; contract or runtime-protocol fixes require new evidence.", + "可执行结论:": "Actionable conclusion:", + "#68 因官方 expected 与 schema 冲突而退出下一轮干净 A/B;#142 因类别验收边界未定义而退出,直至补齐可观察口径;#168 修正本项目的空结果状态协议;#124 增加候选语义核验和提交前自检;#506 修复 HAR 即时采集、末步工具调用 fail-closed,并在预注册的 40 步上限下复测。旧的 25/30 仍保留为历史官方分数,但不能再解释成五个同因的 Agent 失败。": "Task 68 leaves the next clean A/B because the official expected value conflicts with its schema. Task 142 leaves until its category acceptance boundary is observable. Task 168 requires the project's zero-result status protocol to be fixed. Task 124 requires candidate-semantic verification and a pre-submit check. Task 506 requires immediate HAR body capture, fail-closed handling of a final-turn tool call, and a retest under a preregistered 40-step ceiling. The old 25/30 remains the historical official score, but it can no longer be interpreted as five same-cause Agent failures.", + "根治措施:": "Corrective action:", + "共同零分": "Shared zero scores", + "不等于共同根因": "Do not imply a shared cause", + "根因结论:": "Root-cause conclusion:", + "#68 是官方任务合同冲突,#124 是候选语义核验缺失,#142 是类别验收边界未定义,#168 是本项目响应协议错误;#506 的 Chromium 是 HAR 采集误判,Moli 是执行步数与完成确认问题。": "Task 68 is an official task-contract conflict; Task 124 lacks candidate-semantic verification; Task 142 has no defined category acceptance boundary; Task 168 is a response-protocol defect in this project. For Task 506, Chromium was misjudged because of HAR capture, while Moli ran out of decision steps without confirming completion.", + "任务合同问题": "Task-contract issues", + "2 题": "2 tasks", + "#68 schema 冲突;#142 类别口径缺失": "Task 68 has a schema conflict; Task 142 lacks a category criterion.", + "Agent 协议与策略": "Agent protocol and policy", + "#124 未核验候选;#168 空结果状态错误": "Task 124 did not validate candidates; Task 168 used the wrong zero-result status.", + "不同根因的同分": "Equal score, different causes", + "1 题": "1 task", + "#506:Chromium 采集误判;Moli 未确认完成": "Task 506: Chromium collection false negative; Moli completion was not confirmed.", + "处理原则:": "Treatment:", + "合同不稳定的任务退出下一轮样本;代码与协议缺陷修复后只通过新执行验证,不覆盖历史官方分数。": "Contract-unstable tasks leave the next cohort. Code and protocol fixes are validated only by new executions and never overwrite historical official scores.", + "查看五题根因、证据闭环与复测门槛 →": "View root causes, evidence chains, and retest gates for all five tasks →", + "首页 · Browser Use 评测": "Home · Browser Use evaluation", + "Browser Use 评测": "Browser Use evaluation", + "Chromium 与 Moli:Browser Use 能力实测": "Chromium vs. Moli: a Browser Use capability evaluation", + "评测方法 · Browser Use 评测": "Methodology · Browser Use evaluation", + "这 30 题如何部署并由 Agent 执行": "How the 30 tasks were deployed and run by the agent", + "图 1 · 运行器从官方 WebArena‑Verified 镜像启动本地站点;每个试次重建该题所需站点、等待环境控制器与应用健康、清理浏览器状态并登录测试账号。图是流程说明,真实性证据来自下方运行身份及 70 个试次页的事件、HAR、trace 哈希。": "Figure 1. The runner starts local sites from official WebArena-Verified images. Every attempt rebuilds the sites required by the task, waits for environment-control and application health, clears browser state, and signs in with benchmark accounts. The figure explains the flow; the runtime identities and the events, HAR, and trace hashes on all 70 attempt pages provide the evidence.", + "不是“先人工部署一次,再连接一个现成网页”。": "This was not a one-time manual deployment followed by connections to an existing website.", + "本轮运行器直接调用 WebArena‑Verified 的 ContainerManager.start();每次正式执行和诊断重试前都以 reset=True 启动该题所需的官方站点容器,并停止无关站点。环境控制服务报告 ready 后,还要通过连续应用级健康检查;随后才清 Cookie、登录内置测试账号、打开任务起始页并开始计入 Agent 执行。": "The runner directly calls WebArena-Verified's ContainerManager.start(). Before every primary run and diagnostic retry, it starts the official site containers required by that task with reset=True and stops unrelated sites. After environment control reports ready, the app must pass repeated application-level health checks. Only then does the runner clear cookies, sign in with benchmark accounts, open the task's start page, and begin agent execution.", + "也不是固定动作回放。": "This was not a fixed action replay either.", + "预注册文件只含任务 ID、层级和入选理由,不含点击序列。CowAgent 收到自然语言目标后,在最多 30 步内循环读取页面观察、请求模型决策并生成下一次 BrowserTool 调用。Chromium 与 Moli 的试次分别连接到独立 CDP 浏览器容器;每个试次保存模型最终 JSON、Agent 事件流、网络 HAR 和 Playwright trace。": "The preregistration contains task IDs, tiers, and selection rationales, but no click sequences. Given the natural-language objective, CowAgent repeatedly reads the page observation, requests a model decision, and generates the next BrowserTool call for up to 30 steps. Chromium and Moli attempts connect to separate CDP browser containers. Every attempt retains the final model JSON, agent event stream, network HAR, and Playwright trace.", + "站点来源": "Site source", + "环节": "Stage", + "本轮实际做法": "Implementation in this run", + "可核验证据": "Verifiable evidence", + "四个官方自托管镜像:Shopping、Shopping Admin、Reddit、GitLab": "Four official self-hosted images: Shopping, Shopping Admin, Reddit, and GitLab", + "运行清单记录镜像 ID、digest、架构与 WebArena‑Verified 1.2.3 checksum": "The run manifest records image IDs, digests, architectures, and the WebArena-Verified 1.2.3 checksum", + "逐试次准备": "Per-attempt preparation", + "重建题目所需容器 → env-ctrl ready → 连续健康检查 → 清状态并登录": "Rebuild required containers → env-ctrl ready → repeated health checks → clear state and sign in", + "运行器实现与 manifest 的 environment_reset;站点准备失败不会产生可计分试次": "Runner implementation and manifest environment_reset; failed site preparation cannot produce a scored attempt", + "CowAgent + 模型依据当前页面逐步调用 BrowserTool;无动作脚本": "CowAgent and the model call BrowserTool step by step from the current page; no action script", + "每个试次页公开按时间排序的 BrowserTool 轨迹和脱敏事件下载": "Every attempt page publishes the ordered BrowserTool trajectory and a sanitized event download", + "结果验收": "Outcome evaluation", + "最终 JSON 交给 AgentResponseEvaluator;需要副作用的任务同时检查 HAR": "AgentResponseEvaluator checks the final JSON; tasks requiring side effects also inspect the HAR", + "每个试次页公开规则级 expected / actual;原始 HAR 与 trace 仅公开 SHA-256 和字节数以避免泄露登录态": "Every attempt page publishes rule-level expected/actual values; raw HAR and trace files expose only SHA-256 and byte counts to protect authenticated state", + "证据边界:": "Evidence boundary:", + "事件流能证明 CowAgent 在运行时产生了不同的 BrowserTool 调用序列,站点镜像和运行配置能证明本地环境身份;它们不能公开模型隐藏思维,也不把成功轨迹当成唯一标准路径。": "Event streams establish that CowAgent produced BrowserTool call sequences at runtime, while site images and run configuration establish the local environment identity. They do not expose hidden model reasoning or make a successful trajectory the only valid path.", + "查看 Chromium 自主轨迹示例": "View a Chromium autonomous trajectory", + "查看 Moli 自主轨迹示例": "View a Moli autonomous trajectory", + "核对镜像与运行身份": "Verify image and runtime identities", + "自托管真实交互": "Self-hosted interactive websites", + "最终 JSON + 必要网络副作用": "Final JSON plus required network side effects", + "程序化;无 LLM judge": "Programmatic; no LLM judge", + "部署重,最适合内核 A/B": "Heavy deployment; strongest fit for browser A/B testing", + "离线网页快照": "Offline webpage snapshots", + "每步目标元素与操作": "Target element and action at each step", + "程序化动作指标": "Programmatic action metrics", + "评过程模仿": "Evaluates process imitation", + "136 个真实网站": "136 live websites", + "动作历史与最终完成": "Action history and final completion", + "WebJudge + 人工标签": "WebJudge plus human labels", + "漂移、CAPTCHA、judge 偏差": "Drift, CAPTCHA, and judge bias", + "实时网站视觉交互": "Visual interaction with live websites", + "最终回答与执行证据": "Final answer and execution evidence", + "多模态 LLM + 人工核验": "Multimodal LLM plus human verification", + "精确复现较弱": "Weaker exact reproducibility", + "长链多站搜索": "Long-horizon multi-site search", + "带引用研究答案": "Research answer with citations", + "rubric Agent-as-a-Judge": "Rubric Agent-as-a-Judge", + "非 UI 内核首选": "Not the first choice for UI browser cores", + "100 个混合任务": "100 mixed tasks", + "最终结果与轨迹": "Final outcomes and trajectories", + "Google judge LLM": "Google judge LLM", + "云浏览器条件影响可比性": "Cloud-browser conditions affect comparability", + "200 个长链多站任务": "200 long-horizon multi-site tasks", + "rubric 完成度": "Rubric completion", + "模型评审": "Model grading", + "成本高": "High cost", + "Agent / 模型": "Agent / model", + "CowAgent;gpt-5.6-sol;reasoning=low": "CowAgent; gpt-5.6-sol; reasoning=low", + "同题、同数据、同模型、同提示和资源上限,只替换浏览器": "Same task, data, model, prompt, and resource limits; only the browser changes", + "每题×浏览器第一次正式执行;二元 pass@1;逐题配对": "First formal run for each task-browser pair; binary pass@1; task-level pairing", + "首轮失败后最多一次,只做恢复诊断;模型传输失败同请求最多重发一次": "At most one retry after primary failure, used only for recovery diagnosis; at most one resend for a model transport failure", + "不纳入无法稳定验收的实时任务;首轮排除需大型数据卷的 Wikipedia / Map": "Exclude live tasks without stable acceptance; the pilot also excludes Wikipedia and Map because of their large data volumes", + "官方程序化 evaluator;无 LLM judge;人工不得改分": "Official programmatic evaluators; no LLM judge; no manual score changes", + "Agent 最终 JSON": "Agent final JSON", + "先按 JSON schema 和数据类型归一化,再比较 task_type、status 与 retrieved_data;按题可声明列表有序或无序。": "Normalize by JSON schema and data type, then compare task_type, status, and retrieved_data; each task may declare ordered or unordered lists.", + "最终回答符合该题公开答案合同。": "The final answer satisfies the task's public answer contract.", + "没有检查回答是通过哪条 UI 路径得到的,也不评操作效率。": "It does not inspect which UI path produced the answer or evaluate action efficiency.", + "本次浏览器 HAR": "HAR from this browser attempt", + "在网络事件中匹配 URL、方法、状态码以及按需声明的 query、POST、响应字段或 Cookie 条件。": "Match URL, method, status code, and, when declared, query, POST, response-field, or cookie conditions in network events.", + "导航或页面变更产生了指定的可观察副作用。": "Navigation or state changes produced the specified observable side effect.", + "没有要求复刻固定点击序列;未声明的页面状态不在验收范围内。": "It does not require a fixed click sequence; undeclared page state is outside the acceptance scope.", + "一题配置的每个 evaluator 都必须得分 1.0,整题才通过。这里没有 LLM-as-a-judge,也没有人工改分。": "Every evaluator configured for a task must score 1.0 for the task to pass. There is no LLM-as-a-judge and no manual score override.", + "本轮没有观察到浏览器成功率差异;这证明对照流程可比,不证明两个内核等价。": "This run found no browser success-rate difference. It establishes a comparable controlled run, not browser equivalence.", + "归因边界:": "Attribution boundary:", + "任务族覆盖": "Task-family coverage", + "样本同时包含信息提取、页面导航和状态变更任务;类别来自每题官方最终答案合同。": "The sample covers retrieval, navigation, and state-changing tasks. Each category comes from the task's official outcome contract.", + "查看这类任务 →": "View this task family →", + "运行与证据是否完整": "Run and evidence completeness", + "正式调用:": "Formal executions:", + "证据完整性审计:": "Evidence integrity audit:", + "可完成性覆盖:": "Measured-solvability coverage:", + "任务数": "Tasks", + "打开逐题横向对照 →": "Open the task-by-task comparison →", + "记录中的验收差异": "Observed acceptance mismatch", + "已观察到的验收差异:": "Observed acceptance mismatch:", + "证据完整性审计": "Evidence integrity audit", + "已审计": "Audited", + "记录不一致": "Record inconsistencies", + "审计结论": "Audit result", + "先看结论": "Headline result", + "当前结论:": "Current conclusion:", + "本轮没有观察到浏览器成功率差异;这证明对照流程可比,不证明两个内核等价。五个共同失败更值得优先检查 Agent 的跨页聚合与最终状态验证。": "This run found no browser success-rate difference. It establishes a comparable controlled run, not browser equivalence. The five shared failures make cross-page aggregation and final-state verification the first agent-level issues to investigate.", + "单边胜出": "One-browser-only wins", + "个完整配对": "complete pairs", + "失败重试恢复": "Failure retry recoveries", + "不回写主分": "Excluded from pass@1", + "调用与核验状态": "Execution and review status", + "调用完整:": "Execution complete:", + "次计划首轮均有结果;失败项的唯一诊断重试也全部完成。": "planned primary runs have results; every single diagnostic retry after a failure is also complete.", + "独立核验:": "Independent review:", + "个试次;未决": "attempts; unresolved", + "。查看核验轮次 →": ". View review history →", + "三类任务概览": "Three task families", + "只在相同任务族与同一批任务内比较;RETRIEVE、NAVIGATE 与 MUTATE 的合同不同。": "Compare browsers within the same task family and task cohort. RETRIEVE, NAVIGATE, and MUTATE use different acceptance contracts.", + "个任务": "tasks", + "两种浏览器": "Both browsers", + "本类首轮": "primary runs in this family", + "浏览器总览": "Browser overview", + "首轮完成": "Primary coverage", + "首轮通过": "Primary passes", + "失败重试": "Failure retries", + "审查结果": "Review result", + "已核验": "reviewed", + "从结论进入证据": "From result to evidence", + "对零分歧、共同失败和下一轮设计的完整论证。": "A complete interpretation of zero disagreements, shared failures, and the next evaluation design.", + "阅读完整解读 →": "Read the interpretation →", + "筛选并下钻两种浏览器试次。": "Filter tasks and inspect both browsers' attempts.", + "打开任务库 →": "Open the task library →", + "首轮、唯一重试和核验状态。": "Primary runs, the single retry, and review status.", + "打开结果库 →": "Open all results →", + "逐题配对比总分更重要": "Paired tasks are more informative than the aggregate score", + "同一任务的两个首轮结果;重试不参与配对主分。": "Both primary results for each task; retries do not enter the paired primary score.", + "配对结果": "Pair outcome", + "两者都通过": "Both pass", + "仅 Chromium": "Chromium only", + "仅 Moli": "Moli only", + "两者都失败": "Both fail", + "本轮未观察到": "Not observed", + "优先排查共同链路": "Inspect the shared stack first", + "精确 McNemar 双侧 p=1.000。零个不一致配对意味着当前样本无法估计内核方向性效应。": "Exact two-sided McNemar p=1.000. With no discordant pairs, this sample cannot estimate a directional browser effect.", + "结论只适用于本轮预注册样本、Agent、模型与运行配置;展示调整不改变测量结果。": "Conclusions apply only to this preregistered sample, agent, model, and runtime configuration. Presentation changes do not alter measured results.", + "候选 benchmark、选择依据与 30 个预注册任务": "Benchmark candidates, selection rationale, and 30 preregistered tasks", + "先区分最终结果、动作过程和模型裁判,再谈采用率;同一家族不同变体的成绩不能合并。": "First separate final-outcome evaluation, action prediction, and model judging. Scores from different variants of one benchmark family are not interchangeable.", + "公开采用信号 Top 3": "Top three public adoption signals", + "采用次数是公开一手材料中的机构数,不是质量分;未找到 DeepSeek、Kimi 公开的一手 UI browser-use benchmark,不等于内部没有评测。": "Counts are institutions found in primary public sources, not quality scores. No primary public UI browser-use benchmark was found for DeepSeek or Kimi; that does not show they have no internal evaluation.", + "Mind2Web 的拆分不是 easy / medium / hard": "Mind2Web splits are not easy, medium, and hard", + "原始 Mind2Web 使用 cross-task、cross-website、cross-domain 衡量泛化;Online‑Mind2Web 没有统一三档;Mind2Web 2 的 short / medium / long 按动作跨度划分。本报告的简单 5、中等 10、困难 15 是预注册的操作长度层级。": "Original Mind2Web uses cross-task, cross-website, and cross-domain splits. Online-Mind2Web has no common three-level difficulty split. Mind2Web 2 uses short, medium, and long action spans. This report's 5/10/15 easy, medium, and hard groups were preregistered by estimated visible operation length.", + "怎样阅读任务 ground truth": "How to read task ground truth", + "参考状态与可完成性证据": "Reference status and solvability evidence", + "“有成功路径证据”不等于存在唯一标准路径;WebArena‑Verified 不要求复刻人工动作序列。": "Evidence of a successful path does not imply one canonical path. WebArena-Verified does not require replaying a human action sequence.", + "30 个任务": "30 tasks", + "全文搜索": "Full-text search", + "首页": "Home", + "账号": "Accounts", + "任务、意图、答案、错误": "Task, intent, answer, or error", + "任务、意图、验收合同": "Task, intent, or acceptance contract", + "层级": "Tier", + "站点": "Site", + "类型": "Type", + "可完成性": "Solvability", + "本轮已有成功轨迹": "Successful trajectory observed", + "仅官方合同": "Official contract only", + "任务": "Task", + "最少步": "Minimum steps", + "入选理由": "Selection rationale", + "官方任务意图": "Official task intent", + "可完成性证据": "Solvability evidence", + "简单": "Easy", + "中等": "Medium", + "困难": "Hard", + "通过": "Pass", + "失败": "Fail", + "首轮": "Primary", + "重试": "Retry", + "失败诊断重试": "Diagnostic retry", + "无": "None", + "官方任务合同、两种浏览器的实际结果与完整自主执行证据。": "Official task contract, both browsers' measured outcomes, and complete autonomous execution evidence.", + "任务身份": "Task identity", + "本项目层级": "Project tier", + "任务类型": "Task type", + "官方 hard subset": "Official hard subset", + "本轮可完成性证据": "Solvability evidence in this run", + "已有成功轨迹": "Successful trajectory observed", + "尚无成功轨迹": "No successful trajectory observed", + "公开任务意图": "Public task intent", + "官方来源:": "Official source:", + "Ground truth · 官方验收合同": "Ground truth · official acceptance contract", + "没有唯一参考点击路径。": "There is no single reference click path.", + "WebArena‑Verified 允许 Agent 自主选择任意 UI 路径;ground truth 是以下最终回答与必要网络副作用的合取。报告因此比较结果合同,而不把某条人工路径当作标准答案。": "WebArena-Verified lets the agent choose any UI path. Ground truth is the conjunction of the final-answer and required network-side-effect contracts below. The report therefore compares outcome contracts rather than treating a human path as the answer key.", + "程序化验收": "Programmatic acceptance", + "该规则必须得分 1.0。": "This rule must score 1.0.", + "匹配选项与结果 schema": "Matching options and result schema", + "参考状态 · 当前环境是否有成功路径证据": "Reference status · evidence of a successful path in the current environment", + "有。": "Yes.", + "本轮至少一个自主试次通过全部官方合同,因此当前站点快照存在已实测的可完成路径。成功轨迹不是唯一标准路径,也不要求另一浏览器复刻。": "At least one autonomous attempt passed every official contract, so this site snapshot has a measured successful path. That trajectory is not the only valid path, and the other browser need not reproduce it.", + "本轮没有。": "Not in this run.", + "官方任务包给出了确定性验收合同,但两种浏览器的首轮和唯一诊断重试都未通过。因此本报告不能独立证明当前快照存在可完成路径,也不能把失败直接归因给浏览器;需要另做不参与计分的人工或 oracle 可完成性核验。": "The official task bundle provides a deterministic acceptance contract, but both browsers failed the primary run and the single diagnostic retry. This report therefore cannot independently prove that the current snapshot is solvable or attribute the failure directly to the browser. A separate non-scoring human or oracle readiness check is required.", + "两种浏览器的全部正式试次": "All formal attempts from both browsers", + "口径": "Attempt class", + "官方结果": "Official verdict", + "端到端耗时": "End-to-end latency", + "证据": "Evidence", + "查看试次": "View attempt", + "共同失败诊断:": "Shared-failure diagnosis:", + "全部试次结果对照": "All-attempt comparison", + "实际最终回答": "Actual final answer", + "失败规则": "Failed rules", + "为什么这样判": "Verdict rationale", + "执行过程": "Execution", + "评最终结果,不评参考点击路径": "Evaluate final outcomes, not a reference click path", + "候选 benchmark 的验收边界": "Acceptance boundaries of candidate benchmarks", + "评什么": "What it evaluates", + "验收对象": "Acceptance target", + "判分": "Scoring", + "问题": "Limit", + "本轮判分链": "Scoring chain for this run", + "预注册 30 题": "Preregister 30 tasks", + "逐题重置站点": "Reset sites per task", + "CowAgent 自主规划": "CowAgent plans autonomously", + "只替换浏览器": "Change only the browser", + "官方 evaluator 验收": "Run official evaluators", + "确定性证据复核": "Review deterministic evidence", + "固定协议": "Fixed protocol", + "项目": "Item", + "规则": "Rule", + "唯一变量": "Only changed variable", + "计划与实际对照": "Planned and actual controls", + "计划固定任务、数据、模型、提示和资源上限,仅替换浏览器;历史执行的视口实际不一致,不能视为浏览器是唯一变量的实验": "The plan held tasks, data, model, prompt, and resource limits fixed while changing the browser. Actual historical viewports differed, so the browser was not the only experimental variable.", + "历史轨迹记录的视口为 Chromium 780×437/493、Moli 1920×1080。视口差异会影响渲染、CPU 和内存;以下数值与最低值标记仅描述各自实际运行条件,不是统一视口下的资源效率结论。正式效率比较须统一设置后完整重跑。": "Historical traces recorded Chromium viewports of 780×437/493 and Moli viewports of 1920×1080. Viewport differences affect rendering, CPU, and memory. The values and minimum-value markers describe the actual conditions of each run, not resource efficiency at a common viewport. A formal efficiency comparison requires a full rerun with consistent settings.", + "共同成功子集也保留了历史视口差异,不能据此得出统一视口下的资源效率结论。Agent 链路还包含模型推理和页面等待,因此不能解读为纯内核性能。": "The common-success subset also retains the historical viewport differences and cannot establish resource efficiency at a common viewport. Agent-path time includes model inference and page waits, so it does not measure browser-core performance alone.", + "主分": "Primary metric", + "排除": "Exclusions", + "裁判": "Judge", + "两类程序化 evaluator 到底检查什么": "What the two programmatic evaluators check", + "输入": "Input", + "比较方式": "Comparison", + "能证明什么": "What it establishes", + "不能证明什么": "What it does not establish", + "合取规则:": "Conjunction rule:", + "从总体配对下钻到每一次执行": "Drill down from paired outcomes to every execution", + "先定位十个首轮失败的规则与直接原因,再筛选全部 70 次执行并下钻回答、轨迹和日志。": "First locate the exact failed rules for ten primary failures, then filter all 70 executions and inspect answers, trajectories, and logs.", + "汇总": "Summary", + "浏览器": "Browser", + "耗时中位数": "Median latency", + "耗时 P95": "P95 latency", + "Tokens 中位数": "Median tokens", + "耗时包含模型响应、页面等待、站点重置与浏览器执行,不能解释为纯内核速度。": "Latency includes model responses, page waits, site resets, and browser execution. It is not isolated browser-core speed.", + "同题首轮关系对比": "Paired primary-outcome comparison", + "每行固定为同一任务的 Chromium 与 Moli 首轮。关系筛选只改变可见行,不产生新分数。": "Each row fixes Chromium and Moli primary runs for the same task. Relationship filters change visible rows only and do not create new scores.", + "关系": "Relationship", + "共同通过": "Both pass", + "共同失败": "Both fail", + "有分歧": "Disagreement", + "找到": "Showing", + "个配对任务": "paired tasks", + "对照结论": "Pair outcome", + "查看两种浏览器全部试次": "View all attempts for both browsers", + "首轮失败定位": "Primary-failure inventory", + "两种浏览器在同五题失败。下表不是人工猜测:失败环节来自官方 evaluator,补充说明来自 Agent 最终错误和执行记录。": "Both browsers failed the same five tasks. The failure stage comes from the official evaluators; the additional diagnosis comes from the agent's final error and execution record.", + "首轮试次": "Primary attempt", + "直接原因": "Recorded mismatch", + "同题对照": "Same-task comparison", + "任务与两浏览器对照": "Task and both browsers", + "70 个试次": "70 attempts", + "默认只显示失败;可按浏览器、层级、站点、任务类型、首轮/重试、失败环节和全文关键词筛选。": "Failures are shown by default. Filter by browser, tier, site, task type, primary/retry, failure stage, or full-text query.", + "失败环节": "Failure stage", + "最终回答": "Final answer", + "网络副作用": "Network side effect", + "多项": "Multiple stages", + "试次": "Attempt", + "传输重试": "Transport retries", + "从官方 ground truth、实际回答、规则级判定下钻到 CowAgent 的每一步 BrowserTool 调用。": "Follow the official ground truth, actual answer, and rule-level verdict down to every CowAgent BrowserTool call.", + "答案对照": "Answer comparison", + "规则判定": "Rule verdicts", + "执行轨迹": "Trajectory", + "运行与证据": "Runtime and evidence", + "结果与运行": "Outcome and runtime", + "失败定位": "Failure diagnosis", + "自主操作": "Autonomous actions", + "模型 tokens": "Model tokens", + "模型请求": "Model requests", + "传输错误": "Transport errors", + "证据复核": "Evidence review", + "Ground truth 与实际最终回答": "Ground truth and actual final answer", + "官方任务合同": "Official task contract", + "Agent 实际回答": "Agent's actual answer", + "官方 evaluator 逐规则判定": "Official evaluator verdicts by rule", + "所有规则都必须得分 1.0;这里使用 evaluator 已归一化的实际值与期望值直接对照。": "Every rule must score 1.0. This section directly compares the evaluator's normalized actual value with the expected value.", + "官方期望": "Official expectation", + "实际归一化结果": "Normalized actual result", + "断言与错误": "Assertions and errors", + "CowAgent 自主执行轨迹": "CowAgent autonomous trajectory", + "以下顺序来自本次执行的 Agent 事件流,不是固定动作回放。展开可查看调用参数和输出摘录;完整脱敏日志可下载。": "This sequence comes from the run's agent event stream, not a fixed action replay. Expand a step for arguments and an output excerpt; the complete sanitized log is downloadable.", + "调用参数": "Call arguments", + "执行输出节选": "Execution output excerpt", + "运行、日志与证据": "Runtime, logs, and evidence", + "最终回答 JSON": "Final answer JSON", + "Evaluator 明细 JSON": "Evaluator details JSON", + "动作轨迹 JSON": "Trajectory JSON", + "脱敏事件日志 JSON": "Sanitized event log JSON", + "原始证据哈希": "Raw-evidence hashes", + "指标": "Metric", + "值": "Value", + "证据完整性检查": "Evidence completeness checks", + "检查": "Check", + "原始证据文件绑定": "Raw evidence file bindings", + "存在": "Present", + "字节": "Bytes", + "公开策略": "Publication policy", + "返回任务页": "Return to task", + "个试次均完成确定性证据复核": "attempts received deterministic evidence review", + "复核不重新判题,也不是 LLM-as-a-judge;官方 evaluator 仍是唯一成绩来源。": "Review does not rescore tasks and is not LLM-as-a-judge. The official evaluator remains the only scoring authority.", + "核验结论": "Review result", + "记录试次": "Recorded attempts", + "已复核": "Reviewed", + "记分分歧": "Score disagreements", + "未决": "Unresolved", + "复核协议": "Review protocol", + "逐试次覆盖": "Per-attempt coverage", + "结论": "Outcome", + "失败检查数": "Failed check count", + "失败检查项": "Failed checks", + "两条轨道回答不同问题,不能混分": "Two tracks answer different questions and cannot share a score", + "固定 BrowserTool 动作轨道与自主 Agent 轨道都有效,但控制变量和结论边界不同。": "A fixed BrowserTool action track and an autonomous-agent track are both useful, but they control different variables and support different conclusions.", + "仓库中的两个 harness": "The repository's two harnesses", + "轨道": "Track", + "Agent 是否自主": "Agent autonomy", + "回答的问题": "Question answered", + "Browser Use 官方实践": "Browser Use's published evaluation practice", + "公开依据": "Public sources", + "本轮浏览器运行身份、完整任务结果与失败证据入口。": "Runtime identity, complete task results, and failure evidence for this browser.", + "运行身份": "Runtime identity", + "架构": "Architecture", + "镜像 ID": "Image ID", + "首轮成绩": "Primary score", + "首轮耗时中位数": "Median primary latency", + "全部执行记录": "All execution records", + "首轮决定主分;仅失败任务有一次诊断重试,重试不回写主分。": "The primary run determines pass@1. Only failed tasks receive one diagnostic retry, and retries never replace the primary score.", + "复现从公开仓库、预注册题集和固定运行身份开始": "Reproduction starts with the public repository, preregistered cohort, and fixed runtime identities", + "公开包提供脱敏投影;模型回答、Cookie 和完整网络正文不进入 Git 或站点。": "The public bundle provides sanitized projections. Model credentials, cookies, and complete network bodies do not enter Git or the site.", + "浏览器与代码身份": "Browser and code identities", + "公开复现入口": "Public reproduction entry points", + "是代码、题集和锁文件的唯一来源。": "is the canonical source for code, task definitions, and lockfiles.", + "本地站点账号、模型权限与发布权限分开处理": "Local site accounts, model access, and publication access are separate", + "这里只说明权限边界,不展示密钥、Cookie、用户名或个人信息。": "This page describes access boundaries without exposing keys, cookies, usernames, or personal information.", + "权限与安装": "Access and setup", + "范围": "Scope", + "本轮做法": "Configuration in this run", + "取得与安装": "Acquisition and setup", + "外部付费账号": "Paid external account", + "当前发布": "Current release", + "变更": "Change", + "原因": "Reason", + "处理": "Resolution", + "影响": "Impact", + "报告解读": "Interpretation", + "评测集": "Dataset", + "评测方法": "Methodology", + "结果分析": "Results", + "核验记录": "Review", + "方法介绍": "Methods", + "优": "Best", + "更优": "Better", + "单次均值更低": "Lower mean per call", + "通过数更高": "Higher pass count", + "通过率更高": "Higher pass rate", + "同组通过数更高": "Higher pass count in group", + "结果详情导航": "Result detail navigation", + "环境与复现": "Reproduction", + "变更记录": "Changes", + "账号与安装": "Accounts and setup", + "切换主题": "Toggle theme", + "中文": "中文" + }, + "PHRASES_1": { + "模板 ID": "template ID", + "自主轨迹示例": "autonomous trajectory example", + "模型推理强度为低": "low reasoning effort", + "步骤预算耗尽": "Step budget exhausted", + "导航语义错误": "Navigation-semantics error", + "检索覆盖不足": "Incomplete retrieval coverage", + "商品选择错误": "Incorrect product selection", + "候选比较错误": "Candidate-comparison error", + "提交字段错误": "Submission-field error", + "检索策略错误": "Retrieval-strategy error", + "流程未完成": "Incomplete workflow", + "结果去重遗漏": "Result-deduplication failure", + "可恢复错误后提前终止": "Premature stop after recoverable error", + "批量操作遗漏": "Incomplete batch workflow", + "提交字段遗漏": "Submission-field omission", + "路由验收缺陷": "Route-acceptance defect", + "evaluator 实现错误": "Evaluator implementation defect", + "任务与验收矛盾": "Instruction-evaluator contradiction", + "表单验收缺陷": "Form-acceptance defect", + "任务状态与验收矛盾": "State-evaluator contradiction", + "站点保存故障": "Site save failure", + "站点依赖故障": "Site dependency failure", + "站点会话故障": "Site session failure", + "网络证据缺失": "Missing network evidence", + "网络证据匹配缺陷": "Network-evidence matching defect", + "响应证据缺失": "Missing response evidence", + "共享环境并发污染": "Shared-environment concurrency contamination", + "检索遗漏与参考状态差异": "Retrieval omission and reference-state drift", + "保留为目标变量;按轨迹修正策略或工具合同": "Keep as a target-variable signal; improve the strategy or tool contract from the trajectory", + "评测集 / evaluator": "Benchmark / evaluator", + "修正合同并重跑;当前仅纳入官方分数": "Fix the contract and rerun; retain only the official score for now", + "修复依赖或保存链路,通过写入预检后重跑": "Fix the dependency or save path, then rerun after a write preflight", + "保留为浏览器集成信号;补齐 HAR 后重跑": "Keep as a browser-integration signal; rerun after completing HAR capture", + "运行环境": "Runtime environment", + "跨进程锁已上线;受污染任务待重跑": "The cross-process lock is implemented; contaminated tasks await rerun", + "任务与 Agent": "Task and Agent", + "先冻结参考状态,再重跑区分 Agent 错误": "Freeze reference state, then rerun to distinguish Agent errors", + "自托管站点": "Self-hosted site", + "浏览器证据接口": "Browser evidence interface", + "保留为对比信号;通过轨迹修正检索、选择和提交策略。": "Keep as a comparison signal; use trajectories to improve retrieval, selection, and submission.", + "任务 / evaluator": "Task / evaluator", + "修正任务合同或 evaluator,验证后重跑;重跑前不进入干净子集。": "Fix the task contract or evaluator, validate it, and rerun; exclude it from the clean subset until then.", + "修复依赖、会话或保存链路,增加任务级写入预检后重跑。": "Fix the dependency, session, or save path and rerun after adding a task-level write preflight.", + "保留为浏览器集成信号;补齐响应正文或 Referer 采集后重跑。": "Keep as a browser-integration signal; rerun after completing response-body or Referer capture.", + "跨进程串行锁已上线;受污染任务待重跑。": "The cross-process serialization lock is implemented; contaminated tasks await rerun.", + "参考状态 + Agent": "Reference state + Agent", + "先重新冻结参考状态,再重跑区分 Agent 错误。": "Refreeze reference state, then rerun to distinguish Agent errors.", + "该执行没有可公开的 trace 页面画面;错误日志与执行证据仍可下载。": "This execution has no publishable trace page frame; the error log and execution evidence remain available.", + "规则 ": "Rule ", + " 秒": " s", + " 次 BrowserTool 调用": " BrowserTool calls", + " 次调用 / ": " calls / ", + " 次传输尝试": " transport attempts", + "总耗时 ": "Total ", + " 次调用": " calls", + "查看 ": "View ", + " 步轨迹": "-step trajectory", + "成功轨迹": "successful trajectory", + "任务 #": "Task #", + "个试次": " attempts", + "个任务": " tasks", + "个失败结果": " failed results", + "实际": "actual", + "期望": "expected", + "环境准备后、Agent 读取前": "After environment preparation and before the Agent read", + "运行结束后的 evaluator 验收": "Evaluator acceptance after execution", + "运行结束后的网络证据验收": "Network-evidence acceptance after execution", + "最终回答/任务状态": "Final answer/task state", + "BrowserTool 执行异常": "BrowserTool execution error", + "步后达到步骤上限": " steps: step limit reached", + "步后的最终回答/任务状态": " steps: final answer/task state", + "步后的站点响应/保存": " steps: site response/save", + "最后页面画面": "Final page frame", + "该逻辑结果包含": "This logical result contains", + "次物理执行。标记为判分权威的执行决定最终得分;累计耗时、tokens 与 BrowserTool 指标是下列各次执行之和。": "physical execution(s). The scoring-authority execution determines the final score; cumulative latency, tokens, and BrowserTool metrics sum all executions shown below.", + "物理执行": "Physical execution", + " · 判分权威": " · scoring authority", + "判分": "Score", + "Agent 链路": "Agent path", + "模型 tokens": "model tokens", + "(完整内容见脱敏事件日志)": "(see the sanitized event log for the complete content)", + "个物理执行均保存环境准备回执;公开环境摘要保留逐项状态,原始登录态不发布。": "physical executions retain environment-preparation receipts. The public environment summary preserves each status while authenticated session state remains private.", + "个逻辑结果及其物理执行、下载路径和公开文件哈希。": "logical results with their physical executions, download paths, and public-file hashes.", + "暂无发布记录。": "No release has been recorded." + }, + "TEXT_2": { + "90 题 Browser Use 完整结果:Chromium 与 Moli 的成功率、代价与失败根因": "Complete 90-task Browser Use results: Chromium vs. Moli success, cost, and root causes", + "90 题核心指标": "Core metrics for all 90 tasks", + "每个模型×浏览器配置都保留完整 90 题。以上是官方 evaluator 决定的最终逻辑结果;物理执行记录可在单题证据页下钻。": "Every model-browser configuration retains all 90 tasks. These are the final logical outcomes from the official evaluator; physical execution records remain available on each task's evidence page.", + "首页按预注册的 90 题展示四种配置的官方 evaluator 结果。其中 14 题经复核存在验收合同、站点、共享状态或参考状态干扰;这些题仍保留在 90 题官方结果中,另外提供 76 题事后诊断子集。": "The homepage reports official evaluator outcomes for all 90 preregistered tasks across four configurations. Review found acceptance-contract, site, shared-state, or reference-state interference in 14 tasks. They remain in the official 90-task result, while a separate 76-task post-hoc diagnostic subset is also provided.", + "14 题存在外部干扰": "Fourteen tasks contain external interference", + "官方 90 题结果不改分、不缩减分母;另用 76 题事后诊断子集观察排除已知干扰后的差异。": "The official 90-task result is neither rescored nor given a smaller denominator. A separate 76-task post-hoc subset shows differences after removing known interference.", + "90 题官方结果与事后诊断分开看": "Separate the official 90-task result from post-hoc diagnosis", + "首页主结果始终使用完整 90 题官方 evaluator 分母。为了观察排除已知外部干扰后的差异,下表再按整题、四种配置一致排除 14 题,得到 76 题事后诊断子集。": "The homepage always uses all 90 tasks as the denominator for the official evaluator result. To inspect differences after known external interference, the table below removes the same 14 tasks across all four configurations and shows a 76-task post-hoc diagnostic subset.", + "这是事后诊断子集,不是正式分数,也不替代 90 题官方结果。75 个原始失败记录继续保存在结果页和核验记录中。": "This is a post-hoc diagnostic subset, not an official score and not a replacement for the official 90-task result. All 75 original failure records remain available in the result and review pages.", + "完整 90 题的速度与浏览器资源": "Latency and browser resources across all 90 tasks", + "完整 90 题官方结果是本报告的主口径;76 题事后诊断子集只用于分离已知外部干扰,不改写成绩。": "The complete official 90-task result is the report's primary view. The 76-task post-hoc diagnostic subset only separates known external interference and does not rewrite scores.", + "Moli 的内存占用明显更低,但 HAR 完整性、工具耗时和部分任务兼容性还需修复;Chromium 在本轮 Sol 层多通过 3 题,Astra 层通过数相同,但当前每种配置只有一个正式样本,不能外推成总体优势。": "Moli uses substantially less memory, while HAR completeness, tool latency, and some task compatibility still require fixes. Chromium passes three more tasks in the Sol cohort and ties Moli in the Astra cohort, but one formal sample per configuration cannot establish a general advantage.", + "先修复可控的合同、evaluator、站点和共享状态问题,再按同一协议完整重跑 90 题四种配置;历史官方分数在新证据批次出现前保持不变。": "Fix controllable contract, evaluator, site, and shared-state defects first, then rerun all four configurations across the same 90-task protocol. Historical official scores remain unchanged until a new evidence cohort exists.", + "四种配置总览:完整 90 题官方结果": "Four-configuration overview: complete official results for 90 tasks", + "同题全配置对比:完整 90 题": "Same-task comparison across all configurations: complete 90-task set", + "每行是一道预注册任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is one preregistered task, with one column per model-browser configuration. Filters isolate divergent, all-pass, or all-fail outcomes.", + "这里保留预注册 90 题的全部官方结果;其中 14 题已标记外部干扰。默认显示失败;点击“查看诊断”可核对发生位置、程序报错、复核根因、轨迹、画面和日志。": "This inventory retains official results for all 90 preregistered tasks, with 14 tasks marked for external interference. Failures are shown by default; open a diagnosis to inspect the failure point, program error, reviewed cause, trajectory, frames, and logs.", + "Chromium 任务通过": "Chromium tasks passed", + "Moli 任务通过": "Moli tasks passed", + "单边通过": "One-browser pass", + "共同失败": "Both fail", + "结果边界:": "Result boundary:", + "失败根因是报告解读的核心证据": "Root causes are core interpretation evidence", + "官方 evaluator 的错误信息只定位未满足的验收规则;根因核验继续检查任务合同、最终回答、网络记录和自主轨迹。未完成核验前,报告保留自动分类,但不展示推测性根因。": "The official evaluator identifies the unmet acceptance rule. Root-cause review then checks the task contract, final answer, network record, and autonomous trajectory. Until that review is complete, the report retains the automatic classification but shows no speculative cause.", + "根因核验:": "Root-cause review:", + "本轮没有失败结果。": "This run has no failed results.", + "解释配对结果、失败根因和结论边界。": "Interpret paired outcomes, failure root causes, and conclusion boundaries.", + "筛选任务并下钻验收合同。": "Filter tasks and inspect their acceptance contracts.", + "一、先看总分,再看同题分歧": "1. Start with aggregate scores, then inspect task-level disagreements", + "二、失败结果必须追到根因": "2. Every failed result requires a root-cause review", + "三、简单、中等、困难都覆盖,但难度只表示操作长度": "3. Easy, medium, and hard are all covered, but difficulty means operation length only", + "四、选择 WebArena‑Verified 是为了可归因的结果验收": "4. WebArena-Verified was selected for attributable outcome evaluation", + "五、结论只适用于本轮受控样本": "5. Conclusions apply only to this controlled sample", + "程序化 evaluator 先给出失败环节;核验再检查最终执行的任务合同、回答、网络记录和 Agent 轨迹,形成更具体的根因子类与原因。共同失败不默认是共同根因,单边失败也不默认是浏览器缺陷。": "The programmatic evaluator first identifies the failed acceptance stage. Review then checks the score-bearing task contract, answer, network record, and agent trajectory to establish a specific root-cause subtype and reason. A shared failure does not imply a shared cause, and a one-browser failure does not automatically imply a browser defect.", + "本轮没有失败结果,无需生成失败根因记录。": "This run has no failed results, so no failure-cause records are needed.", + "先修复已核验根因并形成新证据,再扩大任务与浏览器范围;历史官方分数不被人工覆盖。": "Fix verified root causes and produce new evidence before expanding the task and browser scope. Historical official scores are never overwritten manually.", + "逻辑结果": "Logical result", + "根因子类": "Root-cause subtype", + "已核验根因": "Verified root cause", + "表 2|根因核验不修改官方分数;修复后必须产生新的执行证据。": "Table 2 | Root-cause review does not alter official scores; fixes require new execution evidence.", + "环境准备证据:": "Environment-preparation evidence:", + "未记录": "Not recorded", + "运行器在每个物理执行前调用": "Before every physical execution, the runner calls", + "逐执行环境准备回执尚不完整;现有记录只能核对运行器配置与镜像身份。": "Per-execution environment-preparation receipts are incomplete; current records can verify only runner configuration and image identity.", + "需要可完成性核验": "Solvability review required", + "已确认失败点": "Confirmed failure points", + "失败结论:": "Failure conclusion:", + "四题的首个可证失败点位于最终答案构造或状态协议,一题在 30 步上限时仍未完成付款提交。现有证据不能把前四题进一步归因到模型、CowAgent 或提示合同中的某一项。": "The first provable failure point is final-answer construction or status encoding in four tasks; one task had not submitted payment when the 30-step limit was reached. The evidence cannot further attribute the first four failures to the model, CowAgent, or prompt contract individually.", + "共享链路诊断": "Shared-stack diagnosis", + "失败归因:": "Failure attribution:", + "5 个共同失败中,四题指向共享 Agent/模型的语义、聚合或输出协议,一题指向共享的步数预算与长链完成;没有发现站点、官方 evaluator 或答案合同错误的证据。": "Among the five shared failures, four point to shared agent/model semantics, aggregation, or output protocol, and one points to the shared step budget and long-horizon completion. No evidence of site, official evaluator, or answer-contract errors was found.", + "这是固定选择的 30 题试验,不是从全部 WebArena‑Verified 任务随机抽样;比例只描述本样本,不外推总体能力。": "This is a fixed 30-task experiment, not a random sample of all WebArena-Verified tasks. Percentages describe this sample only and are not population estimates.", + "共同失败是报告解读的核心证据": "Shared failures are core interpretation evidence", + "25:25 只说明当前样本没有产生浏览器分歧。理解这个结果必须继续看五个共同失败发生在哪个验收点,而不是把平分直接解释成内核等价。": "The 25:25 result means only that this sample produced no browser disagreement. Interpreting it requires locating the acceptance point of each shared failure instead of treating the tie as browser-core equivalence.", + "最终答案数据": "Final-answer data", + "3 题": "3 tasks", + "#68 漏记录;#124 纳入投影仪;#142 多计发箍": "Task 68 omitted a record; Task 124 included a projector; Task 142 added a headband.", + "最终状态协议": "Final-status protocol", + "1 题": "1 task", + "#168 将空结果编码为 SUCCESS": "Task 168 encoded an empty result as SUCCESS.", + "任务完成边界": "Task-completion boundary", + "#506 达到 30 步时尚未提交付款": "Task 506 had not submitted payment at step 30.", + "归因边界:": "Attribution boundary:", + "这些记录确认了失败发生的位置;除 #506 的步数上限外,没有控制变量实验能够继续区分模型判断、CowAgent 策略和提示/响应合同。": "These records establish where acceptance failed. Apart from Task 506's step limit, no controlled experiment in this run can distinguish model judgment, CowAgent policy, and prompt/response contracts.", + "查看五题逐条直接证据与归因边界 →": "View direct evidence and attribution boundaries for all five tasks →", + "逻辑结果:": "Logical results:", + "筛选并下钻两种浏览器结果。": "Filter tasks and inspect results from both browsers.", + "60 个逻辑结果": "60 logical results", + "查看最终判定、耗时、轨迹和核验状态。": "Inspect final verdicts, latency, trajectories, and review status.", + "同一任务的两个浏览器最终逻辑结果。": "Final logical results from both browsers on the same task.", + "零个不一致任务意味着当前固定样本无法估计内核方向性效应;不对全部 Browser Use 任务作统计外推。": "With no discordant tasks, this fixed sample cannot estimate a directional browser effect. It does not support statistical generalization to all Browser Use tasks.", + "评测集· Browser Use 评测": "Dataset · Browser Use evaluation", + "Mind2Web 家族 · 5 家": "Mind2Web family · 5 organizations", + "WebArena 家族 · 3 家": "WebArena family · 3 organizations", + "OpenAI、Anthropic、THUDM / ChatGLM。自托管、可重置、结果验收,更适合浏览器 A/B。": "OpenAI, Anthropic, and THUDM / ChatGLM. Self-hosting, resettable state, and outcome evaluation make it a stronger fit for browser A/B tests.", + "WebVoyager 家族 · 2 家": "WebVoyager family · 2 organizations", + "OpenAI、Google DeepMind。真实网站直观,但网页漂移与 LLM judge 降低精确复现性。": "OpenAI and Google DeepMind. Live websites are intuitive, but page drift and LLM judging reduce exact reproducibility.", + "采用次数来自公开一手材料中的机构去重计数,不是质量分;证据核对日期 2026-09-08。未找到 DeepSeek、Kimi 公开的一手 UI browser-use benchmark,不等于内部没有评测。": "Adoption counts are deduplicated organizations in public primary sources, not quality scores; evidence was checked on 2026-09-08. No primary public UI browser-use benchmark was found for DeepSeek or Kimi, which does not imply that they perform no internal evaluation.", + "WebArena‑Verified 的 ground truth 不是一条固定点击脚本。": "WebArena-Verified ground truth is not a fixed click script.", + "每题声明一个结构化最终回答合同,并在需要确认页面副作用时附加一个或多个网络事件合同。所有合同都通过才记 1 分。任务详情同时展示合同、两种浏览器的actual回答、失败规则和 CowAgent 自主轨迹。": "Each task declares a structured final-answer contract and, when page side effects must be verified, one or more network-event contracts. Every contract must pass for a score of 1. Task details link the contract to both browsers' actual answers, failed rules, and autonomous CowAgent trajectories.", + "题集范围": "Dataset scope", + "本轮在模型执行前固定 30 题,覆盖四个已部署站点以及 RETRIEVE、NAVIGATE、MUTATE 三种最终合同。这里仅描述题目与验收规则;通过率、耗时、轨迹和可完成性观察统一放在结果分析中。": "The 30 tasks were fixed before model execution. They cover four deployed sites and three outcome contracts: RETRIEVE, NAVIGATE, and MUTATE. This page defines tasks and acceptance rules only; pass rates, latency, trajectories, and solvability observations belong in Results.", + "层级 ↕": "Tier ↕", + "站点 ↕": "Site ↕", + "最少步 ↕": "Minimum steps ↕", + "入选理由 ↕": "Selection rationale ↕", + "官方任务意图 ↕": "Official task intent ↕", + "官方 Hard ↕": "Official hard subset ↕", + "任务合同 ↕": "Task contract ↕", + "查看任务合同": "View task contract", + "是": "Yes", + "否": "No", + "先看任务目标与验收结论,再按需展开技术明细和 CowAgent 的每一步 BrowserTool 调用。": "Start with the task objective and acceptance outcome, then expand technical evidence and each CowAgent BrowserTool call as needed.", + "任务与验收": "Task and acceptance", + "查看任务与验收": "View task and acceptance", + "任务要求与验收结果": "Task requirements and acceptance result", + "已确认失败点:": "Confirmed failure point:", + "直接证据:": "Direct evidence:", + "最终答案少了一条必需记录。": "The final answer omitted one required record.", + "两条最终轨迹都已读取《A Christmas Carol》帖子;官方 evaluator 报告 Matched (1/2)。": "Both final trajectories read the A Christmas Carol post; the official evaluator reports Matched (1/2).", + "最终价格聚合纳入了不符合 wireless earphone 语义的商品。": "The final price aggregation included a product that does not satisfy the wireless-earphone semantics.", + "轨迹中的 17,774.32 对应 QFWCJ 4K Short Throw Projector,且与最终错误最大值完全相同。": "In the trajectory, 17,774.32 belongs to a QFWCJ 4K Short Throw Projector and exactly matches the incorrect final maximum.", + "最终求和多计入了 11.99 的 Crystal Headband。": "The final sum incorrectly included the 11.99 Crystal Headband.", + "轨迹列出 50.52 的 conditioner、11.99 的 headband 和 17.99 的 haircolor;80.50=50.52+11.99+17.99,68.51=50.52+17.99。": "The trajectory lists a 50.52 conditioner, an 11.99 headband, and a 17.99 haircolor item; 80.50=50.52+11.99+17.99, while 68.51=50.52+17.99.", + "空结果被编码成错误的最终状态。": "The empty result was encoded with the wrong final status.", + "官方 evaluator 的唯一失败断言是 Expected not_found_error, got success;检索内容同为空。": "The official evaluator's only failed assertion is Expected not_found_error, got success; both sides contain no retrieved item.", + "结账在付款提交之前停止。": "Checkout stopped before payment submission.", + "官方 evaluator 已确认正确商品加入购物车;付款提交与成功页规则失败,最终响应明确记录达到单次运行最大步骤数。": "The official evaluator confirms that the correct product was added to the cart; the payment-submission and success-page rules failed, and the final response explicitly records reaching the run's maximum step count.", + "任务目标": "Task objective", + "Agent 可自主选择 UI 路径;计分只看以下最终回答与必要网络副作用。": "The agent may choose any UI path. Scoring uses only the final answer and required network side effects below.", + "检查 Agent 最终回答的状态、字段和值是否符合官方答案合同。": "Checks whether the status, fields, and values in the agent's final answer satisfy the official answer contract.", + "检查浏览器网络记录中是否出现合同要求的页面副作用。": "Checks whether the browser network record contains the side effect required by the contract.", + "检查该题官方合同声明的结果条件。": "Checks an outcome condition declared by the task's official contract.", + "最终回答符合该规则。": "The final answer satisfies this rule.", + "已观察到满足合同的网络事件。": "A network event satisfying the contract was observed.", + "最终数据与官方期望值不一致。": "The final data differs from the official expectation.", + "浏览器网络记录中没有出现满足该规则的事件。": "No browser network event satisfying this rule was observed.", + "该规则未通过。": "This rule failed.", + "查看官方期望与本次观察": "View the official expectation and this run's observation", + "本次观察": "Observed in this run", + "查看 evaluator 技术明细": "View evaluator technical details", + "所有规则均为官方程序化验收,必须全部得分 1.0。页面先给出可读结论;官方期望、实际观察和断言保留在折叠技术明细及下载文件中。": "All rules are official programmatic checks and every rule must score 1.0. The page leads with a readable conclusion; expectations, observations, and assertions remain in collapsed technical details and downloadable files.", + "证据诊断:": "Evidence-based diagnosis:", + "本诊断不修改官方分数。": "This diagnosis does not change the official score.", + "共享的跨帖聚合与空值处理错误。轨迹已看到两篇符合条件的帖子,说明站点内容存在;失败发生在最终答案整理。": "Shared cross-post aggregation and null-handling error. The trajectory observed both qualifying posts, so the site content was present; the failure occurred while assembling the final answer.", + "共享的跨帖聚合与空值处理错误。轨迹已看到两篇符合条件的帖子,说明站点内容存在;失败发生在最终答案整理。 本诊断不修改官方分数。": "Shared cross-post aggregation and null-handling error. The trajectory observed both qualifying posts, so the site content was present; the failure occurred while assembling the final answer. This diagnosis does not change the official score.", + "共享的搜索结果相关性过滤错误;站内搜索召回过宽是诱因,但 Agent 仍应按任务语义筛选商品。": "Shared search-result relevance filtering error. Broad site-search recall contributed, but the agent still needed to filter products by the task semantics.", + "共享的搜索结果相关性过滤错误;站内搜索召回过宽是诱因,但 Agent 仍应按任务语义筛选商品。 本诊断不修改官方分数。": "Shared search-result relevance filtering error. Broad site-search recall contributed, but the agent still needed to filter products by the task semantics. This diagnosis does not change the official score.", + "共享的商品类别判定错误。订单数据和 evaluator 数值一致,未见站点或计分器异常。": "Shared product-category classification error. Order data and evaluator values agree; no site or scorer anomaly was found.", + "共享的商品类别判定错误。订单数据和 evaluator 数值一致,未见站点或计分器异常。 本诊断不修改官方分数。": "Shared product-category classification error. Order data and evaluator values agree; no site or scorer anomaly was found. This diagnosis does not change the official score.", + "共享的最终状态协议错误,不是检索内容错误,也不是浏览器执行错误。": "Shared final-status protocol error, not a retrieval-content or browser-execution error.", + "共享的最终状态协议错误,不是检索内容错误,也不是浏览器执行错误。 本诊断不修改官方分数。": "Shared final-status protocol error, not a retrieval-content or browser-execution error. This diagnosis does not change the official score.", + "共享的长链任务完成错误:30 步上限前没有完成结账。两种浏览器路径细节不同,但都未越过同一验收终点。": "Shared long-horizon completion error: checkout was not completed within the 30-step limit. Browser paths differed in detail, but neither crossed the same acceptance endpoint.", + "共享的长链任务完成错误:30 步上限前没有完成结账。两种浏览器路径细节不同,但都未越过同一验收终点。 本诊断不修改官方分数。": "Shared long-horizon completion error: checkout was not completed within the 30-step limit. Browser paths differed in detail, but neither crossed the same acceptance endpoint. This diagnosis does not change the official score.", + "环境": "Environment", + "Apple Silicon Mac 状态": "Apple Silicon Mac status", + "完整集单站题": "Full-set single-site tasks", + "hard 单站题": "Hard single-site tasks", + "约束": "Constraint", + "已在本机实跑": "Measured locally", + "可部署,尚未实跑": "Deployable, not yet measured", + "可尝试,尚未验证": "Feasible to try, not yet verified", + "arm64 镜像;需约 100 GB 数据;只出现在跨站任务": "arm64 image; about 100 GB of data; appears only in cross-site tasks", + "amd64 镜像;需约 60 GB 数据与 Apple Silicon 转译": "amd64 image; about 60 GB of data plus Apple Silicon translation", + "官方任务集与安装说明": "Official dataset and installation guide", + "官方环境容量说明": "Official environment capacity guide", + "站点确实部署在本机,不是公网代站。": "The sites were deployed locally, not replaced by public websites.", + "为什么五个失败任务会完全相同": "Why the same five tasks failed in both browsers", + "二、为什么五个失败任务会完全相同": "2. Why the same five tasks failed in both browsers", + "二、五个共同失败,具体失败在哪里": "2. Where the five shared failures occurred", + "25 比 25 的真正含义:五个共同失败点相同,根因尚未隔离": "What 25 vs. 25 really means: the five failure points match, but the causes are not isolated", + "同一 CowAgent 与同一模型替换浏览器后,30 个配对任务没有一个出现单边胜出。四个共同失败止于最终答案或状态协议,一个止于 30 步上限前的结账流程;现有证据可以定位失败点,但不能把隐藏原因单独归给浏览器、模型或 Agent。": "With the same CowAgent and model and only the browser changed, none of the 30 paired tasks produced a one-browser-only win. Four shared failures ended at final-answer or status-protocol checks, and one ended in checkout before the 30-step limit. The evidence locates the failure points but cannot assign the hidden causes separately to the browser, model, or agent.", + "五、下一轮先扩展四个已验证站点": "5. Expand the four verified sites first", + "官方判定差异": "Official verdict mismatch", + "直接证据": "Direct evidence", + "尚不能判定": "Not established", + "表 2|只陈述最终回答、官方 evaluator 与执行轨迹能够直接证明的事实;不修改官方分数。": "Table 2 | Only facts directly established by the final response, official evaluator, and execution trajectory are stated; scores are unchanged.", + "两个浏览器共享 CowAgent、模型、提示与工具合同、站点状态、30 步上限和 evaluator,只有浏览器实现不同。逐条证据只能确认验收链路中的首个失败点;除 506 题明确触发步数上限外,记录不足以把错误进一步归因到模型、CowAgent 策略或提示合同中的某一项。": "The browsers share CowAgent, model, prompt and tool contract, site state, the 30-step limit, and evaluator; only the browser implementation changes. The evidence establishes the first failed acceptance point. Apart from Task 506 explicitly reaching the step limit, the records cannot attribute the errors further to the model, CowAgent policy, or prompt contract individually.", + "#68、#124、#142 和 #168 的确定事实是最终答案构造或状态协议错误;#506 的确定事实是达到 30 步上限时尚未产生付款提交和成功页事件。站点错误、evaluator 实现错误、答案合同错误以及模型内部决策原因,在这批证据中均未被证明。因而本轮不能单独判定为“模型能力不行”,只能判定当前 CowAgent、模型、提示与工具合同的组合在这些验收点失败。": "For Tasks 68, 124, 142, and 168, the established fact is an error in final-answer construction or status encoding. For Task 506, the established fact is that payment submission and the success-page event were still absent when the 30-step limit was reached. This evidence does not establish a site error, evaluator implementation bug, answer-contract error, or hidden model decision cause. The run therefore cannot isolate the result as a model-capability failure; it establishes only that the current CowAgent, model, prompt, and tool-contract combination failed at these acceptance points.", + "两种浏览器都只返回《The Hobbit》,官方合同还要求《A Christmas Carol》(author=null)。": "Both browsers returned only The Hobbit; the official contract also requires A Christmas Carol with author=null.", + "两种浏览器都返回 min=0.01、max=17774.32;官方合同要求 max=298.00。": "Both browsers returned min=0.01 and max=17774.32; the official contract requires max=298.00.", + "两种浏览器都返回 80.50;官方合同要求 68.51。": "Both browsers returned 80.50; the official contract requires 68.51.", + "两种浏览器都返回 SUCCESS + [];官方合同要求 NOT_FOUND_ERROR + null。": "Both browsers returned SUCCESS + []; the official contract requires NOT_FOUND_ERROR + null.", + "最终逻辑结果均已添加正确商品,但没有付款提交事件和下单成功页事件。": "Both final logical results added the correct product but contain neither a payment-submission event nor an order-success-page event.", + "可确认遗漏发生在页面内容已经进入轨迹之后;现有证据不能判定模型为何丢弃该记录。": "The omission occurred after the page content entered the trajectory; the evidence cannot establish why the model discarded it.", + "可确认错误值来自无关投影仪;现有证据不能区分是模型判断、Agent 策略还是任务提示约束不足导致未过滤。": "The incorrect value is established as coming from an unrelated projector; the evidence cannot distinguish model judgment, agent policy, or insufficient prompt constraints as the cause of the missing filter.", + "可确认差额及其来源;现有证据不能判定模型为何把 headband 纳入任务口径。": "The amount and its source are established; the evidence cannot establish why the model included the headband in scope.", + "可确认是最终响应协议不匹配,不是页面导航失败;现有证据不能判定错误映射来自模型还是 Agent 响应封装。": "This is established as a final-response protocol mismatch, not a navigation failure; the evidence cannot determine whether the incorrect mapping came from the model or agent response wrapper.", + "30 步上限是已确认的直接停止条件;现有证据不能进一步量化模型规划、Agent 调度和浏览器工具开销各自消耗了多少不必要步骤。": "The 30-step limit is the confirmed immediate stop condition; the evidence cannot quantify how many unnecessary steps came from model planning, agent scheduling, or browser-tool overhead.", + "实际差异": "Observed mismatch", + "证据支持的诊断": "Evidence-supported diagnosis", + "置信度": "Confidence", + "高": "High", + "中": "Medium", + "表 2|诊断来自最终回答、官方 evaluator 与执行轨迹的交叉核对;不修改官方分数。": "Table 2 | Diagnoses cross-check final answers, official evaluator output, and execution trajectories; scores are unchanged.", + "两个浏览器共享 CowAgent、模型、提示与工具合同、站点状态、30 步上限和 evaluator,只有浏览器实现不同。5 个共同失败都落在共享层,因此结果高度相关并不反常;它更说明这批题没有触发足以改变最终成败的浏览器差异。": "The browsers share CowAgent, model, prompt and tool contract, site state, the 30-step limit, and evaluator; only the browser implementation changes. All five shared failures occurred in shared layers, so correlated outcomes are expected. More importantly, this cohort did not trigger a browser difference large enough to change the verdict.", + "结论:": "Conclusion:", + "现有证据不支持“网站本身报错”“官方 evaluator 实现错误”或“标准答案错误”。其中 #124 的站内搜索召回过宽会增加歧义,但返回结果仍需要按任务语义过滤。更准确的归因是:四题属于共享 Agent/模型的语义、聚合或输出协议问题,一题属于共享的长链完成与步数预算问题。这不是对模型通用能力的定论,只描述当前 CowAgent、模型与运行协议。": "The evidence does not support a broken site, an official evaluator implementation bug, or an incorrect answer key. Broad site-search recall adds ambiguity to Task #124, but results still need semantic filtering. The best-supported attribution is four shared agent/model semantic, aggregation, or output-protocol errors and one shared long-horizon completion and step-budget error. This is not a general verdict on the model; it applies to the current CowAgent, model, and run protocol.", + "Wikipedia 需要约 100 GB 数据,Map 需要约 60 GB 数据且当前公开镜像为 amd64;二者可作为后续扩展,但没有理由成为本机下一批的前置阻塞。": "Wikipedia requires about 100 GB of data. Map requires about 60 GB and its current public image is amd64. Both can be later expansions, but neither should block the next Mac cohort.", + "保留 CowAgent、模型、reasoning=low 和每题最多两次执行的固定协议,优先加入长页面、复杂编辑器、弹窗、多标签页、上传下载等更可能诱发浏览器差异的任务。实时网站另开 Online‑Mind2Web / WebVoyager 泛化轨道,LLM judge 与人工抽检单列。": "Keep CowAgent, the model, reasoning=low, and at most two executions per task fixed. Prioritize long pages, complex editors, dialogs, multiple tabs, uploads, and downloads that are more likely to expose browser differences. Use a separate Online-Mind2Web / WebVoyager generalization track for live sites, with LLM judging and human sampling reported separately.", + "两种浏览器都返回《The Hobbit》,遗漏《A Christmas Carol》(作者允许为空)。": "Both browsers returned The Hobbit but omitted A Christmas Carol, whose author may be null.", + "两者都把搜索结果中的无关投影仪计入 wireless earphone 价格范围,最大值报为 17,774.32,官方期望为 298.00。": "Both browsers included an unrelated projector in the wireless-earphone price range, reporting 17,774.32 instead of the official 298.00 maximum.", + "两者都汇总为 80.50,官方期望为 68.51;差额来自把水晶发箍计入 hair care/style 商品。": "Both browsers totaled 80.50 instead of 68.51; the difference came from treating a crystal headband as a hair-care/style product.", + "两者都正确找到空集合,却返回 SUCCESS + [];合同要求 NOT_FOUND_ERROR + null。": "Both browsers correctly found an empty set but returned SUCCESS + []; the contract requires NOT_FOUND_ERROR + null.", + "两者都选中正确商品并清空旧购物车,但未产生付款提交与成功页事件。": "Both browsers selected the correct product and cleared the old cart, but neither produced payment-submission and success-page events.", + ",其默认路由指向": ", and its default route points to", + ",只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前冻结证据保存了代码提交、运行协议和镜像身份;事件流从环境准备完成后开始,因此逐次 reset、ready 与 health 结果未形成独立绑定的准备回执。新版运行协议已要求保存该回执。": ", retaining only the sites required by the task. After env-ctrl reports ready, the runner performs consecutive application health checks, clears browser state, signs in with test accounts, and opens the start page. The frozen evidence retains the code commit, run protocol, and image identities. Because the event stream begins after preparation, per-run reset, readiness, and health outcomes were not bound into a separate preparation receipt. The new run protocol requires that receipt.", + "表 1|30 个任务的逐题最终结果配对。": "Table 1 | Paired final outcomes for 30 tasks.", + "访问这些端口。这个地址只是本轮私有网络中的宿主入口,不是评测网站或固定产品配置。": "to access the sites. This address is only the host entry point in this run's private network; it is neither a benchmark website nor a fixed product configuration.", + "预注册文件只含任务 ID、层级和入选理由,不含点击序列。CowAgent 收到自然语言目标后,在最多 30 步内循环读取页面观察、请求模型决策并生成下一次 BrowserTool 调用。Chromium 与 Moli 分别连接独立 CDP 浏览器容器;每个逻辑结果保留最终 JSON、Agent 事件流、网络 HAR 和 Playwright trace。": "The preregistration contains task IDs, tiers, and selection rationales, but no click sequence. Given a natural-language objective, CowAgent repeatedly reads the page observation, requests a model decision, and generates the next BrowserTool call for up to 30 steps. Chromium and Moli connect to separate CDP browser containers. Each logical result retains the final JSON, agent event stream, network HAR, and Playwright trace.", + "本轮访问地址": "Address used in this run", + "应用端口": "Application port", + "环境控制端口": "Environment-control port", + "状态": "Status", + "本轮已部署": "Deployed in this run", + "未部署;不在 30 题": "Not deployed; outside the 30 tasks", + "事件流能证明 CowAgent 在准备完成后产生了 BrowserTool 调用序列;镜像 ID、digest、代码提交与网关记录限定了本地运行身份。当前证据没有独立的逐次环境准备 receipt,因此报告不把“每次重置均成功”当成已经独立核验的事实。": "Event streams establish that CowAgent produced BrowserTool calls after preparation. Image IDs, digests, the code commit, and gateway records bound the local runtime identity. The evidence has no independent per-execution preparation receipt, so the report does not treat every reset as independently verified.", + "结果口径": "Result unit", + "每题×浏览器一个最终逻辑结果;二元通过率;逐题配对": "One final logical result per task-browser pair; binary pass rate; task-level pairing", + "执行上限": "Execution limit", + "结果分析 · Browser Use 评测": "Results · Browser Use evaluation", + "从总体配对下钻到每个逻辑结果": "Drill down from the paired summary to every logical result", + "先看 30 个同题配对,再定位 10 个失败结果的官方规则、actual回答、轨迹和日志。": "Start with 30 same-task pairs, then inspect the official rules, actual answers, trajectories, and logs for the 10 failed results.", + "通过率": "Pass rate", + "Agent 耗时中位数": "Median agent latency", + "Agent 耗时 P95": "P95 agent latency", + "BrowserTool 耗时中位数": "Median BrowserTool latency", + "BrowserTool 耗时 P95": "P95 BrowserTool latency", + "Agent 端到端耗时": "Agent end-to-end latency", + "从浏览器会话准备开始,到 CowAgent 完成、evaluator 所需证据落盘前结束;包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": "Measured from browser-session preparation until CowAgent completes and before evaluator evidence is persisted. It includes sign-in, model inference, orchestration, page waits, BrowserTool, and cleanup, but excludes the preceding Docker site reset.", + "BrowserTool 执行耗时": "BrowserTool execution latency", + "是事件流中每次 browser 工具调用墙钟时间之和,包含工具封装、CDP 往返与页面等待,仍不是纯内核 CPU 时间。": "The sum of wall-clock durations for browser tool calls in the event stream. It includes tool-wrapper, CDP round-trip, and page-wait overhead, so it is still not pure browser-core CPU time.", + "浏览器动作耗时拆分": "Browser action latency breakdown", + "BrowserTool 动作延迟": "BrowserTool action latency", + "每张卡对应一种 CowAgent action。以单次调用墙钟均值作为主要比较值并标记较低者;总耗时与调用数用于解释工作量,不判优。该指标仍包含工具封装、CDP 往返和页面等待,不是纯内核 CPU 时间。": "Each card represents one CowAgent action. Mean wall-clock time per call is the primary comparison and the lower value is marked; total latency and call count describe workload and are not ranked. The metric still includes tool-wrapper, CDP round-trip, and page-wait overhead, so it is not pure browser-core CPU time.", + "单次调用墙钟时间": "Wall time per call", + "按 CowAgent 发出的 BrowserTool action 聚合调用次数与墙钟时间,用于定位 navigate、click、input、snapshot 等动作在哪个浏览器上更慢。该指标比 Agent 总耗时更接近浏览器执行层,但仍包含工具与 CDP 开销。": "Aggregate call counts and wall-clock time by BrowserTool action emitted by CowAgent to locate whether navigate, click, input, snapshot, and other actions are slower in one browser. This metric is closer to the browser execution layer than total agent latency, but still includes tool and CDP overhead.", + "动作": "Action", + "浏览器两两比较": "Pairwise browser comparisons", + "新增浏览器后自动生成每一对浏览器的同题比较;每一行只使用双方都完成的任务,避免把三种以上浏览器压成一个含义不清的总胜负。": "Adding a browser automatically creates a same-task comparison for every browser pair. Each row uses only tasks completed by both browsers, avoiding an ambiguous aggregate win/loss label across three or more browsers.", + "浏览器对": "Browser pair", + "共同任务": "Shared tasks", + "左侧单独通过": "Left-only passes", + "右侧单独通过": "Right-only passes", + "同题浏览器结果矩阵": "Same-task browser result matrix", + "浏览器组合对比": "Browser-set comparison", + "按同一任务比较所选浏览器,不用各浏览器不同的成功样本拼接结论。基准浏览器单选,对比浏览器可多选;新增浏览器只增加选项和结果标签,不改变页面结构。": "Compare selected browsers on the same task instead of combining different success subsets. Choose one baseline browser and one or more comparison browsers; adding a browser only adds an option and outcome label without changing the page structure.", + "同题结果组合": "Same-task outcome combinations", + "选择一个基准浏览器和一个或多个对比浏览器;浏览器标签可直达该题完整结果。": "Choose one baseline browser and one or more comparison browsers. Browser labels link directly to the complete task result.", + "浏览器 A": "Browser A", + "比较条件": "Comparison condition", + "A 通过,其余失败": "A passes, all others fail", + "A 失败,其余通过": "A fails, all others pass", + "所选浏览器全部通过": "All selected browsers pass", + "所选浏览器全部失败": "All selected browsers fail", + "仅所选浏览器通过": "Only the selected browsers pass", + "全部类型": "All task types", + "对比浏览器(可多选)": "Comparison browsers (select multiple)", + "通过浏览器": "Passing browsers", + "失败浏览器": "Failing browsers", + "请至少选择一个对比浏览器": "Select at least one comparison browser", + "每行是一道任务,浏览器作为可扩展列生成;以后增加浏览器时沿用同一张矩阵,不再把页面结构写死为 Chromium 与 Moli。关系筛选只改变可见行,不产生新分数。": "Each row is one task, with browsers generated as extensible columns. Future browsers use the same matrix instead of hard-coding Chromium and Moli into the page structure. Relationship filters change visible rows only and create no new scores.", + "全部通过": "All pass", + "全部失败": "All fail", + "结果有分歧": "Mixed results", + "横向关系": "Cross-browser outcome", + "任务定义": "Task definition", + "任务合同": "Task contract", + "失败结果定位": "Failed-result inventory", + "默认只显示失败;可按浏览器、层级、站点、任务类型、失败环节和全文关键词筛选。": "Failures are shown by default. Filter by browser, tier, site, task type, failure stage, or full-text query.", + "动作数": "Actions", + "查看逐规则判定": "View rule-level verdicts", + "累计自主操作": "Cumulative autonomous actions", + "Agent 链路耗时包含会话准备、登录、模型推理、调度、工具执行和收尾,不包含 Docker 站点重置;BrowserTool 耗时是全部 browser 工具调用的墙钟时间之和,不等同于纯内核时间。": "Agent-stack latency includes session preparation, sign-in, model inference, orchestration, tool execution, and cleanup, but excludes Docker site reset. BrowserTool latency is the sum of wall-clock time for all browser tool calls and is not pure browser-core time.", + "执行未完成;错误信息见运行与证据。": "Execution did not complete; see runtime and evidence for the error.", + "Agent 端到端耗时包含会话准备、登录、模型推理、调度、工具执行和收尾,不包含 Docker 站点重置;BrowserTool 执行耗时是全部 browser 工具调用的墙钟时间之和,不等同于纯内核时间。": "Agent end-to-end latency includes session preparation, sign-in, model inference, orchestration, tool execution, and cleanup, but excludes Docker site reset. BrowserTool execution latency is the sum of wall-clock time for all browser tool calls and is not pure browser-core time.", + "公开包包含脱敏的回答、evaluator、事件流与轨迹。原始 HAR 和 Playwright trace 可能携带登录态、Cookie、页面快照或请求正文,因此不公开;其字节数和 SHA-256 保留用于证明证据绑定。": "The public bundle contains sanitized answers, evaluator records, event streams, and trajectories. Raw HAR and Playwright traces may contain authenticated state, cookies, page snapshots, or request bodies, so they are withheld; byte counts and SHA-256 hashes retain the evidence binding.", + "整次 Agent 运行的容器计量只作诊断;不同进程模型下不直接比较内存或 PID。": "Whole-run container measurements are diagnostic only; memory and PID are not compared directly across different process models.", + "全部官方 evaluator 均通过。": "All official evaluators passed.", + "核验记录 · Browser Use 评测": "Review · Browser Use evaluation", + "证据完整性与独立结果复核是两个指标;本轮只完成前者,官方 evaluator 仍是唯一成绩来源。": "Evidence integrity and independent result review are separate metrics. This run completed only the former; the official evaluator remains the sole scoring authority.", + "证据完整性": "Evidence integrity", + "文件、哈希与分数一致性": "File, hash, and score consistency", + "独立结果复核": "Independent result review", + "尚未进行语义盲审": "No semantic blind review performed", + "完整性未决": "Integrity issues unresolved", + "完整性检查确认回答、HAR、Agent trace、事件和 evaluator 明细存在,并核对哈希、分数、状态与官方版本。": "Integrity checks confirm that answers, HAR, agent traces, events, and evaluator details exist, and verify hashes, scores, statuses, and the official version.", + "复核边界:": "Review boundary:", + "本轮没有第二名人工或模型对结果做盲审,因此“0 个完整性未决”不能解读为结果已经独立语义复核。": "No second human or model blind-reviewed the results, so '0 unresolved integrity issues' must not be interpreted as independent semantic review.", + "审计项": "Audit item", + "回答、事件、HAR、trace 与 evaluator 文件完整且哈希绑定": "Answers, events, HAR, traces, and evaluator files are complete and hash-bound", + "成绩来源": "Scoring authority", + "只接受未修改的 WebArena‑Verified evaluator 输出": "Accept only unmodified WebArena-Verified evaluator output", + "一致性": "Consistency", + "公开状态、分数、官方版本与原始记录一致": "Published status, score, and official version match the source record", + "争议处理": "Dispute handling", + "任何不一致或缺失证据均阻止发布;人工不得改分": "Any inconsistency or missing evidence blocks release; humans may not change scores", + "逐结果覆盖": "Per-result coverage", + "方法介绍 · Browser Use 评测": "Participants · Browser Use evaluation", + "本轮 A/B 的唯一变化项是浏览器实现;CowAgent、模型、任务、资源上限、站点与 evaluator 都是共同实验条件。": "The browser implementation is the only changed variable in this A/B test. CowAgent, the model, tasks, resource limits, sites, and evaluators are shared experimental conditions.", + "两个参评浏览器": "Two browser participants", + "参评浏览器": "Browser participant", + "运行版本": "Runtime version", + "接入方式": "Integration", + "本轮成绩": "Result in this run", + "独立容器 · CDP · CowAgent BrowserTool": "Dedicated container · CDP · CowAgent BrowserTool", + "更完整地说,一个可复现的“参评方法”是整套 Agent 栈;但本轮所有栈字段都保持一致,只有浏览器后端不同,所以报告按浏览器命名选手。不能把 CowAgent、gpt‑5.6‑sol 或 WebArena‑Verified 也算成独立选手。": "Strictly, a reproducible participant is the whole agent stack. In this run every stack field is held constant except the browser backend, so participants are named by browser. CowAgent, gpt-5.6-sol, and WebArena-Verified are not additional participants.", + "共同实验条件": "Shared experimental conditions", + "栈字段": "Stack field", + "本轮固定值": "Fixed value in this run", + "角色": "Role", + "自主观察、规划并调用 BrowserTool": "Observe, plan, and call BrowserTool autonomously", + "模型": "Model", + "为两个选手生成决策": "Generate decisions for both participants", + "任务与站点": "Tasks and sites", + "同一 30 题;同一批本地站点镜像": "Same 30 tasks; same local site images", + "提供一致输入与网页状态": "Provide identical inputs and website state", + "动作接口": "Action interface", + "把 Agent 动作交给浏览器": "Deliver agent actions to the browser", + "验收": "Evaluation", + "WebArena‑Verified 1.2.3 官方 evaluator": "Official WebArena-Verified 1.2.3 evaluator", + "对最终 JSON 与必要网络副作用判分": "Score final JSON and required network side effects", + "资源与上限": "Resources and limits", + "相同 CPU、内存、viewport 与最多 30 步": "Same CPU, memory, viewport, and 30-step limit", + "控制运行预算": "Control the execution budget", + "这 30 题由 CowAgent 自主执行。": "CowAgent executes all 30 tasks autonomously.", + "仓库中的 Browser Trajectory Replay(轨迹重放)固定动作微基准属于另一条实验轨道,不是本报告的参评选手,也不计入这 30 题成绩。": "The repository's fixed-action Browser Trajectory Replay microbenchmark is a separate experimental track, not a participant in this report, and is excluded from these 30 task results.", + "Browser Use 公开使用 BU Bench V1、Odysseys、Online‑Mind2Web 和 Stealth Bench。可借鉴的是运行前确认任务可完成、保存逐题 trace、断点续跑,并把站点不可完成与 Agent 真失败分开。Stealth Bench 测反机器人检测,不能替代自主任务成功率。": "Browser Use publicly uses BU Bench V1, Odysseys, Online-Mind2Web, and Stealth Bench. Useful practices include confirming task solvability before execution, retaining per-task traces, resuming interrupted runs, and separating unsolvable sites from genuine agent failures. Stealth Bench measures bot detection and cannot replace autonomous task success rate.", + "环境与复现 · Browser Use 评测": "Reproduction · Browser Use evaluation", + "协议": "Protocol", + "运行配置选择集 SHA-256": "Run-config selection SHA-256", + "当前选择集 SHA-256": "Current selection SHA-256", + "部署环境与网络拓扑": "Deployment environment and network topology", + "对应的网关地址访问这些端口。运行时记录的地址是": "gateway corresponding to host.docker.internal inside each container. The address recorded at runtime is", + "层": "Layer", + "本轮身份": "Identity in this run", + "作用": "Purpose", + "宿主": "Host", + "承载站点和浏览器容器": "Hosts site and browser containers", + "浏览器到宿主网关": "Browser-to-host gateway", + "访问站点容器发布端口": "Accesses published ports from site containers", + "ARM 兼容模式": "ARM compatibility mode", + "未启用": "Disabled", + "运行部分 amd64 PHP 站点镜像": "Runs selected amd64 PHP site images", + "站点镜像": "Site image", + "镜像引用": "Image reference", + "环境隔离与并行边界": "Environment isolation and parallelism boundary", + "修正后的运行器把一个 Docker daemon 及其固定站点端口视为一个环境 lane。同一 lane 内的任务和浏览器必须串行:每次尝试先重置站点,再完成 Agent、证据收尾和 evaluator,整个周期由跨进程锁保护。缺少 Docker daemon 身份时直接停止,不降级为无锁执行。": "The corrected runner treats one Docker daemon and its fixed site ports as one environment lane. Tasks and browsers in the same lane run serially: each attempt resets the sites first, then completes the Agent run, evidence finalization, and evaluation while one cross-process lock protects the full lifecycle. If the Docker daemon identity is unavailable, execution stops instead of falling back to an unlocked run.", + "运行方式": "Execution mode", + "是否允许": "Allowed", + "隔离要求": "Isolation requirement", + "一套站点环境,Chromium 与 Moli 串行": "One site stack; Chromium and Moli run serially", + "允许,默认方案": "Allowed; default", + "同一 daemon 锁;每次尝试前重置并核对初态": "One daemon-scoped lock; reset and verify initial state before every attempt", + "一套站点环境,两个浏览器并行": "One site stack; two browsers run in parallel", + "不允许": "Not allowed", + "账号不同仍会共享对象、索引、缓存和后台任务": "Separate accounts still share objects, indexes, caches, and background jobs", + "多个任务并行": "Multiple tasks in parallel", + "允许": "Allowed", + "每个 worker 使用独立 daemon、站点容器、网络、端口和可写卷;镜像只读层可复用": "Each worker uses an independent daemon, site containers, network, ports, and writable volumes; read-only image layers may be shared", + "逐执行的环境准备 receipt 尚未包含在这批冻结证据中;现有记录可核对运行器配置与镜像身份,但不能独立重放每次 reset/ready/health 的完整证明链。": "Per-execution environment-preparation receipts are absent from this frozen evidence. Existing records verify runner configuration and image identities but cannot independently replay the complete reset/ready/health proof chain for every execution.", + "每个逻辑结果的脱敏回答、evaluator、事件和轨迹位于": "Sanitized answers, evaluator records, events, and trajectories for each logical result are under", + "给出全部 60 个逻辑结果的页面与下载路径。": "lists page and download paths for all 60 logical results.", + "账号 · Browser Use 评测": "Accounts · Browser Use evaluation", + "不展示密钥、Cookie 或真实个人信息;官方合同中的内置测试邮箱和用户名作为合成 ground truth 保留。": "Keys, cookies, and real personal information are not shown. Built-in test emails and usernames in official contracts remain as synthetic ground truth.", + "权限与费用": "Access and cost", + "四个自托管站点": "Four self-hosted sites", + "官方 Docker 镜像与内置测试账号;每题前重置": "Official Docker images and built-in test accounts; reset before each task", + "不需要": "Not required", + "模型 API": "Model API", + "需要相应 API 权限": "Corresponding API access required", + "当前团队无权限": "Current team lacks access", + "只发布脱敏静态报告": "Publishes only the sanitized static report", + "发布者需已有站点权限": "Publisher must already have site access", + "字段": "Field", + "任务成绩": "Task score", + "逻辑结果耗时中位数": "Median logical-result latency", + "版本与镜像身份用于限定复现边界,不参与评分。": "Version and image identity bound reproducibility and do not affect scoring.", + "全部逻辑结果": "All logical results", + "每题只展示一个最终逻辑结果;端到端耗时与模型用量包含获得该结果所需的全部执行。": "Each task shows one final logical result. End-to-end latency and model usage include every execution needed to obtain it.", + "Agent 耗时": "Agent latency", + "BrowserTool 耗时": "BrowserTool latency", + "失败原因": "Failure reason", + "程序自动定位": "Automatic failure locator", + "核验根因子类": "Verified root-cause subtype", + "核验原因": "Verified cause", + "最终回答验收失败": "Final-answer acceptance failure", + "网络副作用验收失败": "Network-side-effect acceptance failure", + "多项验收失败": "Multiple acceptance failures", + "60 个任务×浏览器结果均保留最终回答、官方 evaluator 结果、Agent 事件和证据哈希。": "All 60 task-browser results retain the final answer, official evaluator result, agent events, and evidence hashes.", + "60/60 个逻辑结果已覆盖,0 个未决。它不重新判题,也不是独立语义复核。": "Evidence-integrity checks cover all 60 logical results with no unresolved integrity issue. They do not rescore tasks or constitute independent semantic review.", + "60/60 个逻辑结果完成证据完整性审计": "Evidence integrity checked for all 60 logical results", + "每题声明一个结构化最终回答合同,并在需要确认页面副作用时附加一个或多个网络事件合同。所有合同都通过才记 1 分。任务页只解释合同;各浏览器的实际回答、失败规则和 CowAgent 自主轨迹统一放在结果页。": "Each task declares a structured final-answer contract and, where required, one or more network-event contracts for page side effects. Every contract must pass for a score of 1. Task pages explain only the contract; browser answers, failed rules, and CowAgent trajectories appear on result pages.", + "运行器代码在每个执行前调用": "Before each execution, the runner calls", + "先看 30 个同题配对,再定位 10 个失败结果的官方规则、实际回答、轨迹和日志。": "Start with the 30 same-task comparisons, then inspect the official rules, actual answers, trajectories, and logs for the 10 failed results.", + "Agent 链路耗时中位数": "Median agent-stack latency", + "Agent 链路耗时 P95": "P95 agent-stack latency", + "Agent 链路累计耗时": "Cumulative agent-stack latency", + "Agent 链路累计": "agent-stack cumulative", + "BrowserTool 累计耗时": "Cumulative BrowserTool latency", + "BrowserTool 累计": "BrowserTool cumulative", + "把获得该任务结果实际发生的执行相加;每次从浏览器会话准备开始,到 CowAgent 完成、验收所需证据落盘前结束,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": "sums all execution used to obtain the task result. Each execution runs from browser-session preparation until CowAgent completes and acceptance evidence is persisted. It includes sign-in, model inference, orchestration, page waits, BrowserTool, and cleanup, but excludes the preceding Docker site reset.", + "是事件流中全部 browser 工具调用墙钟时间之和,包含工具封装、CDP 往返与页面等待,仍不是纯内核 CPU 时间。": "is the sum of wall-clock durations for all browser tool calls in the event stream. It includes wrapper, CDP round-trip, and page-wait overhead, so it is not pure browser-core CPU time.", + "次调用": "calls", + "次动作": "actions", + "次模型请求": "model requests", + "如何增加下一名选手": "How to add another participant", + "原版 Mind2Web、Online-Mind2Web 与 Mind2Web 2 的任务和评分器不同,不能合并到同一成绩表。": "Original Mind2Web, Online-Mind2Web, and Mind2Web 2 use different tasks and graders and must not be merged into one score table.", + "新增浏览器时,需要注册其容器镜像、版本与 CDP 端点,并在同一冻结任务集和共同条件下形成完整结果。总榜按浏览器逐行扩展;同题矩阵按浏览器增加列;统计比较按浏览器对单独生成,不能把多个浏览器压成一个总胜负。": "Register the new browser's container image, version, and CDP endpoint, then complete the same frozen task set under the shared controls. The leaderboard adds a browser row, the task matrix adds a browser column, and statistical comparisons are generated per browser pair rather than collapsing multiple browsers into one win/loss label.", + "先看总体与同题比较,再通过筛选下钻到每个结果的官方规则、实际回答、轨迹和日志。": "Start with aggregate and same-task comparisons, then use filters to inspect each result's official rules, actual answer, trajectory, and logs.", + "Agent 链路耗时": "Agent-stack latency", + "均值": "Mean", + "总耗时": "Total latency", + "单次均值": "Mean per call", + "调用数": "Call count", + "更优": "Better", + "标记方向明确且数值更优的指标:通过数与通过率越高越优,耗时与 Tokens 越低越优;数值相同时不标记。": "marks metrics with a clear direction and a better value: higher pass counts and rates are better, while lower latency and token usage are better. Ties are not marked.", + "按 CowAgent 发出的 BrowserTool action 聚合总墙钟时间、单次均值与调用数。耗时较低者标记为更优;调用数描述 Agent 行为,不判优。该指标比 Agent 总耗时更接近浏览器执行层,但仍包含工具与 CDP 开销。": "Aggregate total wall-clock time, mean per call, and call count by BrowserTool action emitted by CowAgent. Lower latency is marked as better; call count describes agent behavior and is not ranked. This metric is closer to the browser execution layer than total agent latency but still includes tool and CDP overhead.", + "核验与修订记录": "Review and revision history", + "按轮次记录核验范围、发现、正式处理和遗留事项;单个任务的判定证据位于结果详情页。": "Each round records its scope, findings, formal corrections, and remaining items. Per-task verdict evidence remains on result detail pages.", + "当前状态": "Current status", + "发布门禁:": "Release gate:", + "证据文件、哈希、分数、状态、官方版本与报告链接已通过程序化一致性检查。": "Evidence files, hashes, scores, statuses, the official version, and report links passed programmatic consistency checks.", + "官方 WebArena‑Verified evaluator 是成绩来源;核验记录只说明报告和实现经过了哪些检查与修订,不替代任务判分。": "The official WebArena-Verified evaluator is the scoring authority. This history records checks and revisions to the implementation and report; it does not replace task scoring.", + "核验轮次": "Review rounds", + "核验范围": "Scope", + "发现": "Findings", + "处理": "Corrections", + "遗留事项": "Remaining items", + "已处理": "Resolved", + "无阻塞项。": "No blocking items.", + "多浏览器比较与结果详情核验": "Multi-browser comparison and result-detail review", + "同题比较交互、BrowserTool 动作延迟和单结果详情首屏。": "Same-task comparison interaction, BrowserTool action latency, and the opening section of result-detail pages.", + "同题视图虽动态生成列,但交互仍以两浏览器关系为中心;动作表层级拥挤且对不同调用量的总耗时判优;完整 evaluator 错误被放入窄指标卡。": "The same-task view generated columns dynamically but still centered its interaction on a two-browser relationship; the action table was visually congested and ranked totals with unequal call counts; full evaluator errors appeared in narrow KPI cards.", + "同题比较改为基准浏览器加多选对比浏览器和明确关系条件;动作延迟改为按 action 分卡并只比较单次均值;详情首屏只保留简明规则定位,完整 expected、actual 和错误进入规则证据区。": "Changed same-task comparison to one baseline browser, multi-select comparison browsers, and explicit outcome relations; split action latency into action cards and compare only mean per call; kept only concise rule labels in the result summary and moved complete expected, actual, and error evidence to the rule section.", + "当前仅有两个参评浏览器;扩展行为已由三浏览器测试模型覆盖,新增真实浏览器仍需形成完整同题结果。": "Only two browser participants are currently measured. A three-browser test model covers extensibility behavior, while any additional real browser still requires complete same-task results.", + "参评浏览器 · Browser Use 评测": "Browser participants · Browser Use evaluation", + "被测浏览器": "Browsers tested", + "被测浏览器与固定实验条件": "Browsers tested and fixed experimental controls", + "本报告比较浏览器。每个模型单独统计,CowAgent、任务、资源上限、网站和 evaluator 均保持一致。": "This report compares browsers. Results are calculated separately for each model, while CowAgent, tasks, resource limits, sites, and evaluators remain fixed.", + "浏览器与版本": "Browsers and versions", + "浏览器": "Browser", + "通过任务": "Tasks passed", + "每个模型分别比较各浏览器,不把不同模型的结果合并排名。": "Browsers are compared separately within each model; results from different models are not merged into one ranking.", + "增加新的浏览器": "Adding another browser", + "仓库中的 Browser Trajectory Replay(轨迹重放)是另一项固定动作测试,不计入这里的成绩。": "The repository's Browser Trajectory Replay track is a separate fixed-action test and is not included in these scores.", + "评测结果": "Evaluation results", + "浏览器 B": "Browser B", + "同一任务上的结果差异": "Same-task outcome differences", + "每行比较同一模型、同一任务和同一网站状态下的两个浏览器。A、B 对应表内所列浏览器。": "Each row compares two browsers under the same model, task, and site state. A and B identify the browsers shown in that row.", + "三类任务的覆盖与成绩": "Coverage and results by task type", + "信息提取、页面导航和页面操作使用不同验收合同,因此分类型展示,不把难度或任务类型混成新的加权分。": "Information retrieval, navigation, and page changes use different acceptance contracts, so they are reported separately without creating a new weighted score.", + "数据是否齐全": "Data completeness", + "检查项": "Check", + "每个失败结果都有分类和具体原因": "Every failed result has a category and a specific cause", + "完整性检查没有发现待处理项": "The integrity check found no unresolved issue", + "包含正式执行及按协议发生的一次重试": "Includes the main execution and the single retry allowed by the protocol", + "总体成绩和耗时": "Overall results and latency", + "报告分别使用 GPT-5.6 Sol 和 GPT-6 Astra 驱动 CowAgent。每组都在相同任务、网站和验收规则下比较浏览器。": "CowAgent was driven by GPT-5.6 Sol and GPT-6 Astra. Within each model run, browsers were compared on the same tasks, sites, and acceptance rules.", + "WebArena‑Verified 1.2.3 · 官方程序化 evaluator · 不使用 LLM judge": "WebArena-Verified 1.2.3 · official programmatic evaluator · no LLM judge", + "在 Sol 运行中,Chromium 比 Moli 多通过 3 题;在 Astra 运行中,两者都是 71/90。耗时需要结合下表和逐题记录看:Agent 总耗时还包含模型推理与页面等待,BrowserTool 耗时也包含工具封装和 CDP 往返。": "In the Sol run, Chromium passed three more tasks than Moli; in the Astra run, both passed 71/90. Latency must be read with per-task records: agent total time includes model inference and page waits, while BrowserTool time still includes tool wrapping and CDP round trips.", + "只标记同一模型下数值唯一更好的浏览器。通过数越高越好;耗时和 Tokens 越低越好。Agent 总耗时包含模型推理、页面等待和浏览器操作,BrowserTool 耗时更接近浏览器执行层,但仍包含工具封装与 CDP 往返。": "marks only the uniquely better browser within the same model. Higher pass counts are better; lower latency and token use are better. Agent total time includes model inference, page waits, and browser actions. BrowserTool time is closer to the browser execution layer but still includes tool wrapping and CDP round trips.", + "打开逐题对照 →": "Open the task-by-task comparison →", + "官方 evaluator 说明哪条验收规则没有满足;下表来自对任务要求、最终回答、网络记录和自主执行轨迹的逐条核验。": "The official evaluator identifies the unmet acceptance rule. The table comes from reviewing each task requirement, final answer, network record, and autonomous trajectory.", + "查看逐条核验原因 →": "View every verified cause →", + "作为分层变量为各浏览器生成决策": "Generates browser decisions within each separately reported model run", + "从模型分层的浏览器对照读出下一轮该怎么做": "What the browser comparisons imply for the next run", + "步骤预算耗尽": "Step budget exhausted", + "导航语义错误": "Navigation-semantics error", + "检索覆盖不足": "Incomplete retrieval coverage", + "商品选择错误": "Incorrect product selection", + "候选比较错误": "Candidate-comparison error", + "提交字段错误": "Submission-field error", + "检索策略错误": "Retrieval-strategy error", + "流程未完成": "Incomplete workflow", + "结果去重遗漏": "Result-deduplication failure", + "可恢复错误后提前终止": "Premature stop after recoverable error", + "批量操作遗漏": "Incomplete batch workflow", + "提交字段遗漏": "Submission-field omission", + "路由验收缺陷": "Route-acceptance defect", + "evaluator 实现错误": "Evaluator implementation defect", + "任务与验收矛盾": "Instruction-evaluator contradiction", + "表单验收缺陷": "Form-acceptance defect", + "任务状态与验收矛盾": "State-evaluator contradiction", + "站点保存故障": "Site save failure", + "站点依赖故障": "Site dependency failure", + "站点会话故障": "Site session failure", + "网络证据缺失": "Missing network evidence", + "网络证据匹配缺陷": "Network-evidence matching defect", + "响应证据缺失": "Missing response evidence", + "共享环境并发污染": "Shared-environment concurrency contamination", + "检索遗漏与参考状态差异": "Retrieval omission and reference-state drift", + "同题比较只在同一模型内进行。A、B 是表内所列的两个浏览器。": "Same-task comparisons are made only within one model. A and B identify the two browsers listed in the row.", + "每行只比较同一模型、同一任务和同一网站状态下的结果。": "Each row compares results under the same model, task, and site state.", + "两个模型的结果不能直接当成模型排名": "The two model runs are not a model ranking", + "evaluator 的报错只说明哪条验收规则没有满足。我们继续核对任务要求、最终回答、网络记录和 Agent 轨迹,确认失败发生在 Agent、任务规则、测试网站、共享环境或浏览器证据记录中的哪一处。": "Evaluator errors identify the unmet acceptance rule. The review then checks the task requirement, final answer, network record, and agent trajectory to locate the failure in the agent, task contract, test site, shared environment, or browser evidence capture.", + "查看每个失败结果的核验结论": "View the verified cause for every failed result", + "人工核验用于解释官方失败结果,不修改分数。修复后需要重新运行才能产生新成绩。": "Review explains official failures without changing scores. A fix affects results only after the task is run again.", + "样本覆盖简单、中等和困难任务": "The sample covers short, medium, and long tasks", + "为什么使用 WebArena‑Verified": "Why WebArena-Verified was selected", + "原版 Mind2Web 主要检查离线动作预测。Online‑Mind2Web 和 WebVoyager 使用实时网页,网页内容会变化,部分评分还依赖模型裁判。WebArena‑Verified 可以本地部署、逐题重置,并用最终答案和网络副作用做程序化验收,适合只替换浏览器的对照实验。": "The original Mind2Web mainly tests offline action prediction. Online-Mind2Web and WebVoyager use live pages, where content drifts and some scoring relies on model judges. WebArena-Verified can be self-hosted and reset per task, with programmatic checks of final answers and required network side effects, which suits a browser-only comparison.", + "它的代价是部署较重,环境故障也会影响结果;官方 evaluator 不评价操作路径是否高效,也覆盖不了开放互联网的全部情况。": "Its costs are a heavier deployment and exposure to environment failures. The official evaluator does not score path efficiency or cover the full open web.", + "这组结果能说明什么": "What these results support", + "下一步": "Next step", + "按核验结论分别修复 Agent、评测任务、测试网站和浏览器证据记录,再重新运行受影响的任务;新增浏览器时继续使用同一组任务和验收规则。": "Fix the agent, benchmark tasks, test sites, and browser evidence capture according to the verified causes, then rerun affected tasks. New browsers should use the same task set and acceptance rules.", + "Chromium 与 Moli 作为浏览器后端参与同题对照;Agent、模型、任务、资源上限、站点与 evaluator 保持一致。": "Chromium and Moli participate as browser backends in a same-task comparison; the agent, model, tasks, resource limits, sites, and evaluators are held constant.", + "参评浏览器是本轮唯一实验变量;完整 Agent 栈的其余组成列为统一实验条件。": "The browser participant is the only experimental variable in this run; all other components of the agent stack are shared controls.", + "逻辑结果耗时 P50": "P50 logical-result latency", + "WebArena‑Verified 当前提供 Shopping、Shopping Admin、Reddit、GitLab、Wikipedia、Map 六类环境。本轮 30 题只覆盖前四类;Wikipedia 与 Map 需要额外大型数据卷,因此没有部署,也没有任务进入成绩。": "WebArena-Verified currently provides six environments: Shopping, Shopping Admin, Reddit, GitLab, Wikipedia, and Map. This 30-task run covers only the first four. Wikipedia and Map require additional large data volumes, so they were not deployed and contribute no scored tasks.", + "只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前冻结证据保存了代码提交、运行协议和镜像身份;事件流从环境准备完成后开始,因此逐次 reset、ready 与 health 结果未形成独立绑定的准备回执。新版运行协议已要求保存该回执。": "and keeps only the sites required by the task. After env-ctrl reports ready, it performs repeated application-health checks, clears browser state, signs in with test accounts, and opens the start page. The frozen evidence records the code commit, protocol, and image identities; events begin after preparation, so per-execution reset, ready, and health outcomes do not have independently bound preparation receipts. The current protocol now requires that receipt.", + "报告模型与比较口径核验": "Report model and comparison review", + "统计摘要、优胜表达、结果下钻、核验记录和参评浏览器说明。": "Summary statistics, winner presentation, result drill-down, review history, and browser-participant description.", + "统计标签重复表达 P50;比较表缺少方向一致的优胜标记;结果页存在重复失败清单;核验页承担了逐结果清单职责。": "The statistics duplicated P50 terminology; comparison tables lacked directionally consistent winner marks; the results page duplicated the failure inventory; and the review page carried a per-result listing.", + "统一为均值、P50、P95;按指标方向突出唯一优胜者;删除重复失败清单;核验页改为按轮次记录范围、发现、处理和遗留事项。": "Standardized on mean, P50, and P95; marked unique winners by metric direction; removed the duplicate failure inventory; and changed the review page to round-based scope, findings, corrections, and remaining items.", + "当前固定样本没有浏览器单边通过任务;优胜标记主要反映耗时与 Tokens,不改变官方任务成绩。": "The fixed sample has no browser-only pass. Winner marks therefore mainly reflect latency and token usage and do not change official task scores.", + "部署证据与计时边界核验": "Deployment evidence and timing-boundary review", + "环境拓扑与网关含义缺少集中说明;历史总耗时边界描述不准确;逐执行环境准备没有独立回执。": "The environment topology and gateway lacked a consolidated explanation; the historical total-latency boundary was inaccurate; and per-execution environment preparation had no independent receipt.", + "补充部署流程图、端口和镜像身份;区分 Agent 链路与 BrowserTool 墙钟时间;新版 runner 保存环境准备和分阶段计时。": "Added the deployment flow, ports, and image identities; separated Agent-stack and BrowserTool wall time; and made the current runner persist environment preparation and phase timings.", + "本批冻结结果不具备逐执行准备回执,报告仅按现有证据声明可复现边界。": "This frozen run lacks per-execution preparation receipts, so the report states reproducibility boundaries only to the extent supported by existing evidence.", + "运行器与证据完整性核验": "Runner and evidence-integrity review", + "模型响应校验、BrowserTool 清理、HAR 留存、证据目录和恢复执行。": "Model-response validation, BrowserTool cleanup, HAR retention, evidence directories, and resume behavior.", + "异常清理可能遮蔽原始错误;HAR 停止操作缺少幂等保护;异常响应和原始证据目录需要更严格保护。": "Cleanup exceptions could mask primary failures; HAR stop lacked idempotency; and malformed responses and raw-evidence directories needed stricter protection.", + "隔离清理异常、使 HAR 停止幂等、加强模型响应结构校验,并阻止原始浏览器证据进入 Git。": "Isolated cleanup failures, made HAR stop idempotent, strengthened model-response validation, and prevented raw browser evidence from entering Git.", + "统一比较统计与核验记录": "Standardize comparison statistics and review history", + "统一比较统计与可扩展浏览器对比": "Standardize comparison statistics and scalable browser comparison", + "比较摘要统一展示均值、P50、P95;同题比较采用基准浏览器与多选浏览器组合;动作延迟按 action 分卡并只对单次均值判优;结果详情将长 evaluator 证据移出指标卡。Chromium 与 Moli 的官方成绩仍均为 25/30。": "Comparison summaries now show mean, P50, and P95. Same-task comparison uses one baseline browser and multiple comparison browsers. Action latency is grouped by action and ranks only mean per call, while long evaluator evidence has moved out of result KPI cards. Official scores remain 25/30 for both Chromium and Moli.", + "比较摘要统一展示均值、P50、P95,并按指标方向突出唯一优胜者;结果页删除与筛选结果重复的失败清单;核验页改为按轮次记录核验与修订。Chromium 与 Moli 的官方成绩仍均为 25/30。": "Comparison summaries now show mean, P50, and P95 and mark unique winners according to metric direction. The results page no longer duplicates the filterable failure inventory, and the review page records review and revision rounds. Official scores remain 25/30 for both Chromium and Moli.", + "统计摘要": "Summary statistics", + "耗时和 Tokens 的集中趋势与尾部表现未采用统一展示结构。": "Latency and token central tendency and tail behavior did not use a consistent presentation.", + "每个指标统一展示均值、P50、P95;P50 不再以另一个名称重复出现。": "Each metric now shows mean, P50, and P95; P50 is no longer duplicated under another label.", + "只改变统计展示,官方任务分数、分母和证据不变。": "Only statistical presentation changed; official task scores, denominators, and evidence are unchanged.", + "比较表达": "Comparison presentation", + "读者需要逐格判断数值方向。": "Readers previously had to infer metric direction cell by cell.", + "通过数与通过率按更高值标优,耗时与 Tokens 按更低值标优;平局和调用次数保持中性。": "Higher pass counts and rates and lower latency and token usage are marked as better; ties and call counts remain neutral.", + "优胜标记是描述性比较,不改变任务判定。": "Winner marks are descriptive comparisons and do not change task verdicts.", + "页面职责": "Page responsibilities", + "失败清单与结果筛选重复,核验页混入逐结果覆盖表。": "The failure inventory duplicated result filters, and the review page included a per-result coverage table.", + "结果库保留成功/失败筛选和任务详情;核验页只呈现核验轮次、处理和遗留事项。": "The result library retains pass/fail filters and task details; the review page now contains only review rounds, corrections, and remaining items.", + "减少重复内容,不删除结果证据。": "Duplicate presentation was removed without deleting result evidence.", + "多浏览器对比": "Multi-browser comparison", + "同题矩阵会随浏览器数量横向增宽,关系筛选也无法表达一个基准浏览器与多个对比浏览器的组合。": "The same-task matrix widened with every browser, and its relationship filter could not express one baseline browser against multiple comparison browsers.", + "改为基准浏览器单选、对比浏览器多选和明确结果关系;任务行按通过与失败浏览器分组,浏览器标签链接完整结果。": "Changed to one baseline browser, multi-select comparison browsers, and explicit outcome relations. Task rows group passing and failing browsers, with browser labels linking to complete results.", + "页面结构可直接容纳新增浏览器;当前结果和分数不变。": "The page structure now accepts additional browsers directly; current results and scores are unchanged.", + "动作延迟与详情页": "Action latency and result details", + "动作表把三项指标压入浏览器单元格,并对调用量不同的总耗时判优;详情首屏的长错误文本破坏阅读层级。": "The action table compressed three metrics into each browser cell and ranked totals despite unequal call counts; long error text disrupted the hierarchy of result-detail pages.", + "每个 action 使用独立比较卡,只以单次均值判优并将总耗时、调用数保持中性;详情首屏只显示简明失败规则,完整诊断保留在逐规则证据区。": "Each action now has a dedicated comparison card. Only mean per call is ranked, while total latency and call count remain neutral. Result summaries show concise failed-rule labels, with complete diagnostics retained in rule-level evidence.", + "修正比较解释与页面层级,不改变测量值、判分或证据。": "This corrects comparison interpretation and page hierarchy without changing measurements, verdicts, or evidence." + }, + "TEXT_3": { + "Browser Use 联合多模型能力实测": "Browser Use capability evaluation across model cohorts", + "浏览器 Browser Use 能力对比": "Browser capability comparison on Browser Use tasks", + "先看结论": "Key findings", + "失败根因是报告解读的核心证据": "Failure root causes are core interpretation evidence", + "任务族覆盖": "Task-family coverage", + "单边通过": "One-browser-only passes", + "共同失败": "Both browsers failed", + "不等于共同根因": "Not necessarily the same root cause", + "模型内完整配对": "complete within-model pairs", + "本轮共有 9 个模型内不一致配对,且方向随模型层变化;固定样本不支持对全部 Browser Use 任务作统计外推。": "This run contains nine discordant within-model pairs, and their direction changes across model cohorts. The fixed sample does not support statistical generalization to all Browser Use tasks.", + "浏览器后端是参评选手;模型是分层实验变量,每个模型层内保持 Agent、任务、资源上限、站点与 evaluator 一致。": "Browser backends are the participants. Models are stratification variables; within each model cohort, the agent, tasks, resource limits, sites, and evaluators are held constant.", + "官方任务身份、公开意图与程序化验收合同。运行结果位于结果分析,不混入评测集定义。": "Official task identity, public intent, and programmatic acceptance contract. Measured outcomes appear in Results and are kept separate from the dataset definition.", + "查看本题运行结果": "View measured results for this task", + "评测集页面保持为冻结输入;实测输出、判分和轨迹在结果页查看。": "Dataset pages remain frozen inputs. Measured outputs, verdicts, and trajectories appear on result pages.", + "Chromium 结果": "Chromium results", + "Moli 1.1.3 结果": "Moli 1.1.3 results", + "全部浏览器对照": "Compare all browsers", + "程序自动定位:": "Automatic failure locator:", + "核验根因子类:": "Reviewed root-cause subtype:", + "核验原因:": "Reviewed cause:", + "查看修正依据、处理与影响": "View findings, treatment, and impact", + "每个版本只记录影响读者判断的正式修正;结论、分母、耗时与证据影响直接展示,技术依据按需展开。": "Each release records only formal corrections that affect reader interpretation. Effects on conclusions, denominators, latency, and evidence are shown directly, with supporting details available on demand.", + "判分权威": "Scoring authority", + "物理执行": "Physical execution", + "模型层": "Model cohort", + "正式处理": "Formal treatment", + "未命名版本": "Untitled release", + "未命名核验": "Untitled review", + "官方 Hard": "Official hard subset", + "步起": "minimum steps", + "模板 ID": "template ID", + "公开文件": "Public files", + "没有 evaluator 明细。": "No evaluator details are available.", + "没有可展示的 BrowserTool 调用。": "No BrowserTool calls are available to display.", + "最终回答 JSON": "Final answer JSON", + "完整执行证据 JSON": "Complete execution evidence JSON", + "Evaluator 明细 JSON": "Evaluator details JSON", + "动作轨迹 JSON": "Action trajectory JSON", + "脱敏事件日志 JSON": "Sanitized event log JSON", + "原始证据哈希": "Source-evidence hashes", + "返回任务页": "Return to the task page", + "任务与验收": "Task and acceptance", + "执行轨迹": "Execution trajectory", + "运行与证据": "Runtime and evidence", + "查看任务合同": "View task contract", + "查看任务与验收": "View task and acceptance", + "基准浏览器": "Baseline browser", + "对比浏览器": "Comparison browsers", + "完整配对": "Complete pairs", + "仅基准通过": "Baseline only", + "仅对比通过": "Comparison only", + "模型内浏览器配对结果": "Within-model browser pair outcomes", + "失败根因以当前证据绑定的逐结果核验为准": "Failure causes follow the per-result reviews bound to the current evidence", + "37/37 个失败结果已逐个绑定当前证据并完成核验。": "All 37 failed results are bound to the current evidence and have completed root-cause review.", + "查看逐结果根因与证据边界 →": "View per-result causes and evidence boundaries →", + "220/220 个物理执行均记录 reset、ready、应用健康检查和登录准备状态;事件流随后证明 CowAgent 自主产生 BrowserTool 调用序列。": "All 220 physical executions record reset, readiness, application-health, and sign-in preparation status. The subsequent event streams show CowAgent generating BrowserTool calls autonomously.", + ",只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。本轮 220/220 个物理执行都保存了环境准备回执,并与对应执行记录绑定。": ", keeping only the sites required by the task. After env-ctrl reports ready, the runner performs repeated application-health checks, clears browser state, signs in with test accounts, and opens the start page. All 220 physical executions retain preparation receipts bound to their execution records.", + "本轮 220/220 个物理执行均保存环境准备回执;公开环境摘要保留逐项状态,原始登录态不发布。": "All 220 physical executions retain environment-preparation receipts. The public environment summary preserves each status while authenticated session state remains private.", + "各模型任务成绩": "Task scores by model cohort", + "各模型逻辑结果耗时 P50": "P50 logical-result latency by model cohort", + "判分权威执行的原始证据绑定": "Source-evidence binding for the authoritative execution", + "Chromium 与 Moli 的 Browser Use 能力对比": "Chromium vs Moli on Browser Use tasks", + "评测结果": "Results", + "同一任务上的结果差异": "Outcomes on the same tasks", + "三类任务的覆盖与成绩": "Coverage and results by task type", + "数据是否齐全": "Data completeness", + "Agent 总耗时": "Agent total time", + "同题数": "Matched tasks", + "两边都通过": "Both passed", + "A 通过、B 失败": "A passed, B failed", + "A 失败、B 通过": "A failed, B passed", + "两边都失败": "Both failed", + "原因所在": "Cause area", + "失败结果数": "Failed results", + "具体分类": "Detailed categories", + "说明": "Explanation", + "Agent 决策或操作": "Agent decisions or actions", + "任务或 evaluator": "Task or evaluator", + "测试网站": "Test website", + "浏览器证据记录": "Browser evidence capture", + "共享测试环境": "Shared test environment", + "多项原因": "Multiple causes", + "检索不完整、候选选择错误、走错页面、字段遗漏,或没有在步骤上限内完成。": "Incomplete retrieval, incorrect candidate selection, wrong navigation, missing fields, or failure to finish within the step limit.", + "任务要求与验收条件冲突,或 evaluator 的路由、表单和实现规则有缺陷。": "The task conflicts with its acceptance conditions, or the evaluator has route, form, or implementation defects.", + "自托管网站的依赖、会话或保存接口失败。": "A dependency, session, or save endpoint failed in a self-hosted test site.", + "页面操作可能成功,但网络记录缺少响应正文或 Referer,evaluator 无法确认。": "The page action may have succeeded, but missing response bodies or Referer fields prevented evaluator confirmation.", + "并发运行改写了同一网站状态;运行器现已串行化,历史结果仍按原证据保留。": "Concurrent runs changed shared site state. The runner now serializes execution; historical results remain bound to their original evidence.", + "同一结果同时包含 Agent 遗漏和冻结参考状态差异。": "The same result includes both an Agent omission and a difference from the frozen reference state.", + "任务类型": "Task type", + "测什么": "What it measures", + "信息提取": "Information retrieval", + "页面导航": "Page navigation", + "页面操作": "State-changing action", + "从页面读取、筛选或汇总信息,不修改站点状态": "Read, filter, or summarize page information without changing site state", + "打开符合指定条件的页面或列表": "Open a page or list matching the requested conditions", + "提交表单、修改记录、结账等会改变站点状态的任务": "Submit forms, edit records, check out, or perform other state-changing actions", + "检查项": "Check", + "任务结果": "Task results", + "失败原因核验": "Failure-cause review", + "未解决的证据问题": "Unresolved evidence issues", + "至少成功过一次的任务": "Tasks with at least one successful run", + "保存的实际执行": "Saved executions", + "每个失败结果都有分类和具体原因": "Every failed result has a category and a specific reviewed cause", + "完整性检查没有发现待处理项": "Integrity checks found no unresolved items", + "包含正式执行及按协议发生的一次重试": "Includes score-bearing executions and the one retry permitted by the protocol", + "报告解读": "Interpretation", + "环境与复现": "Environment and reproduction", + "总体成绩和耗时": "Overall scores and latency", + "同一任务上的结果": "Outcomes on the same tasks", + "两个模型的结果不能直接当成模型排名": "The two model runs are not a model ranking", + "失败原因": "Failure causes", + "样本覆盖简单、中等和困难任务": "The sample covers short, medium, and long interaction paths", + "为什么使用 WebArena‑Verified": "Why WebArena-Verified", + "这组结果能说明什么": "What these results support", + "下一步": "Next steps", + "结果编号": "Result ID", + "原因分类": "Cause category", + "核验结论": "Reviewed cause", + "查看每个失败结果的核验结论": "View the reviewed cause for every failed result", + "人工核验用于解释官方失败结果,不修改分数。修复后需要重新运行才能产生新成绩。": "Human review explains official failures without changing scores. A fix requires a new run to produce a new result." + }, + "TEXT_4": { + "打开一个需要登录的 GitLab 目标页": "Open an authenticated GitLab target page", + "打开一份报表并读取第一个条目": "Open a report and read its first entry", + "打开评论列表并读取一种状态的数量": "Open a review list and read the count for one status", + "搜索一张发票并读取总金额": "Search for an invoice and read its total", + "进入一个论坛、选择最新帖子并点赞": "Enter a forum, choose the newest post, and upvote it", + "进入项目议题列表并应用排序和状态筛选": "Open a project's issue list and apply sorting and status filters", + "把需求转成商品查询并打开匹配详情页": "Turn a requirement into a product query and open the matching detail page", + "进入订单历史,经翻页或排序后读取最早日期": "Open order history and read the earliest date after paging or sorting", + "找到项目贡献者活动页并限定一个日期": "Find a project's contributor activity page and constrain it to one date", + "打开个人项目并检查星标与项目标识": "Open a personal project and inspect its star and project identifier", + "按标题和更新时间筛选个人议题,再读取状态": "Filter personal issues by title and update time, then read status", + "按库存量筛选后台表格并收集 SKU": "Filter an admin table by inventory and collect SKUs", + "按状态筛选订单,打开最新一单并读取总额": "Filter orders by status, open the newest order, and read its total", + "筛选并排序订单,再打开最新的匹配客户": "Filter and sort orders, then open the newest matching customer", + "打开个人资料设置,修改简介并保存": "Open profile settings, edit the biography, and save", + "找到最新论坛帖子,并按作者和票数条件检查多条评论": "Find the newest forum post and inspect multiple comments by author and vote criteria", + "检查一整年的分页订单并汇总数量和支出": "Inspect a full year of paginated orders and total their count and spend", + "检查十个排序帖子,并根据描述归类推荐内容": "Inspect ten sorted posts and classify recommendations from their descriptions", + "检查十个排序帖子,并从合格描述中提取两个字段": "Inspect ten sorted posts and extract two fields from qualifying descriptions", + "应用日期和状态条件,并汇总五个月度数量": "Apply date and status conditions and total five monthly counts", + "应用日期和状态条件,并汇总十一个月度数量": "Apply date and status conditions and total eleven monthly counts", + "搜索商品、检查结果页并计算价格区间": "Search products, inspect the result page, and calculate a price range", + "结合搜索词排名与独立的商品可用性检查": "Combine query ranking with an independent product-availability check", + "检查日期区间内的订单、归类商品并汇总行项目金额": "Inspect orders in a date range, classify products, and total line-item amounts", + "筛选库存、检查变体并返回成对商品字段": "Filter inventory, inspect variants, and return paired product fields", + "排序已完成订单、检查两份详情并合计付款": "Sort completed orders, inspect two details, and total payments", + "检查两组各四笔订单,并计算付款差额": "Inspect two groups of four orders and calculate the payment difference", + "找到最新完成订单,并规范化、排序多个行项目": "Find the latest completed order, then normalize and sort multiple line items", + "清空购物车,按预算和评分筛选品类,选择商品并结账": "Empty the cart, filter a category by budget and rating, choose a product, and check out", + "定位项目文件、修改标题、提交到默认分支并验证": "Locate a project file, change its title, commit to the default branch, and verify", + "报告解读 · Browser Use 评测": "Interpretation · Browser Use evaluation", + "从 25 比 25 读出下一轮该怎么做": "What the 25-to-25 result implies for the next run", + "完整文章与首页共用同一份测量数据;另提供无 JavaScript 的独立导出版。": "The full article and home page use the same measured data. A standalone JavaScript-free export is also available.", + "独立导出": "Standalone export", + "打开独立文章": "Open standalone article", + "下载论点证据": "Download claim evidence", + "Browser Use 实测解读": "Browser Use evaluation interpretation", + "25 比 25 不是“内核等价”:当前样本没有产生分歧": "25 to 25 does not mean browser-core equivalence: this sample produced no disagreement", + "同一 CowAgent 与同一模型替换浏览器后,30 个配对任务没有一个出现单边胜出。这个结果证明对照流程跑通,却没有为内核优劣提供方向性证据。": "With the same CowAgent and model and only the browser changed, none of the 30 paired tasks produced a one-browser-only win. The controlled workflow ran successfully, but the result provides no directional evidence about browser-core superiority.", + "测量口径:WebArena‑Verified 1.2.3;每题×浏览器一个最终逻辑结果;官方确定性 evaluator;无 LLM judge。": "Measurement unit: WebArena-Verified 1.2.3; one final logical result per task-browser pair; official deterministic evaluators; no LLM judge.", + "一、最重要的结果不是平分,而是零个不一致配对": "1. The key result is not the tie but the absence of discordant pairs", + "Chromium 与 Moli 分别通过 25/30 和 25/30。逐题配对后,25 题共同通过、5 题共同失败,两个浏览器单边通过都是 0。由于固定样本里没有不一致任务,这批数据不能估计浏览器的方向性效应,也不对 WebArena‑Verified 全集作统计外推。": "Chromium and Moli each passed 25/30. Pairing by task gives 25 joint passes, five joint failures, and zero one-browser-only passes. Because this fixed sample contains no discordant tasks, it cannot estimate a directional browser effect or support statistical generalization to the full WebArena-Verified set.", + "表 1|30 任务的逐题最终结果配对。": "Table 1 | Paired final results for 30 tasks.", + "可支持的判断": "Supported conclusion", + "当前组合可完成": "Current stack can complete", + "严谨表述只能是“本轮没有观察到成功率差异”,不能写成“两种内核等价”。30 题对小效应检出力有限;更关键的是,样本没有诱发内核分歧。": "The defensible statement is only that this run observed no success-rate difference, not that the browser cores are equivalent. Thirty tasks have limited power for small effects; more importantly, this sample did not elicit browser-core disagreement.", + "二、共同失败是待核验线索,不是已证实根因": "2. Shared failures are leads to verify, not confirmed root causes", + "5 个共同失败任务均未满足官方验收合同。下表只陈述官方expected与actual结果的差异,不把差异直接归因给 Agent、浏览器或站点。": "All five shared-failure tasks missed the official acceptance contract. The table states only the difference between official expected and actual results and does not directly attribute it to the agent, browser, or site.", + "表 2|任务详情公开官方合同、actual回答、逐规则判定与执行轨迹。": "Table 2 | Task details expose the official contract, actual answer, per-rule verdicts, and execution trajectory.", + "公开诊断": "Published diagnosis", + "官方expected包含多本书;四次执行的actual回答都只包含一本。": "The official expected value contains multiple books; the actual answer in all four executions contains only one.", + "actual回答中的价格范围与该题官方目标商品范围不一致。": "The price range in the actual answer differs from the official target-product range.", + "actual订单汇总与官方expected值不一致。": "The actual order aggregate differs from the official expected value.", + "actual回答为空列表,而官方expected为非空项目列表。": "The actual answer is an empty list, while the official expected value is a non-empty project list.", + "官方网络副作用合同未通过;购物车最终状态未满足题目要求。": "The official network-side-effect contract failed; the final cart state did not satisfy the task.", + "这五题在本轮没有任何successful trajectory,因此当前站点快照的可完成性也未被本报告独立证明。下一步应先做不参与计分的人工或 oracle 可完成性核验,再区分 Agent、工具抽象、步数上限、站点状态和浏览器因素。": "None of these five tasks has a successful trajectory in this run, so the report does not independently establish solvability on the current site snapshot. The next step is a non-scoring human or oracle solvability check, followed by separating agent, tool abstraction, step-limit, site-state, and browser factors.", + "三、简单、中等、困难都覆盖了,但“难度”只是操作长度": "3. Easy, medium, and hard are covered, but difficulty means operation length", + "WebArena‑Verified 没有官方 easy/medium/hard 三档。本项目在运行前按最少可见 UI 操作数注册为 1–4、5–7、8+ 三档,配额 5/10/15。它控制操作链覆盖,不等同于推理难度。": "WebArena-Verified has no official easy/medium/hard tiers. Before execution, this project registered tasks by minimum visible UI actions as 1-4, 5-7, and 8+, with quotas of 5/10/15. This controls operation-chain coverage and is not equivalent to reasoning difficulty.", + "表 3|各层级最终通过数/任务数。": "Table 3 | Final passes/tasks by tier.", + "本项目分层": "Project tier", + "表 4|四个可重置自托管站点。": "Table 4 | Four resettable self-hosted sites.", + "四、选择 WebArena‑Verified 是为了归因,不是因为它最流行": "4. WebArena-Verified was chosen for attribution, not popularity", + "Mind2Web 家族采用面更广,但原版主要评离线动作预测;Online‑Mind2Web 和 WebVoyager贴近实时网页,却引入网页漂移和模型裁判。WebArena‑Verified 可以自托管、逐题重置,并用结构化最终答案和必要网络副作用做程序化验收,更适合“只替换浏览器”的 A/B。": "The Mind2Web family has broader adoption, but its original version mainly evaluates offline action prediction. Online-Mind2Web and WebVoyager are closer to live browsing but introduce page drift and model judging. WebArena-Verified is self-hosted, resettable per task, and programmatically evaluates structured final answers and required network side effects, making it better suited to a browser-only A/B test.", + "代价也明确:部署重,环境兼容本身会成为工程变量;官方 evaluator 不衡量路径效率,也不能覆盖开放互联网泛化。": "The tradeoff is substantial deployment, with environment compatibility becoming an engineering variable. The official evaluator does not measure path efficiency or open-web generalization.", + "五、下一轮应主动寻找内核敏感样本": "5. The next run should actively seek browser-sensitive tasks", + "保留 CowAgent、模型、reasoning=low 和每题最多两次执行的固定协议,扩大稳定可验收的配对样本;新增长页面、上传下载、复杂编辑器、弹窗、多标签页等更可能诱发内核差异的任务。实时网站另开 Online‑Mind2Web / WebVoyager 泛化轨道,LLM judge 与人工抽检单列。": "Keep the fixed CowAgent, model, reasoning=low, and at-most-two-executions protocol while expanding the stable, evaluable paired sample. Add long pages, uploads/downloads, complex editors, dialogs, multiple tabs, and other tasks more likely to elicit browser differences. Use a separate Online-Mind2Web / WebVoyager generalization track for live sites, with LLM judging and human spot checks reported separately.", + "决策边界": "Decision boundary", + "本轮支持“流程可比、当前样本无方向性差异”;不支持“内核等价”“Moli 已全面替代 Chromium”或“端到端耗时就是纯内核速度”。Astra 获权后必须形成独立运行。": "This run supports a comparable workflow and no directional difference in the current sample. It does not support browser-core equivalence, full replacement of Chromium by Moli, or interpreting end-to-end latency as pure browser-core speed. Astra requires a separate run once access is available." + }, + "TEXT_5": { + "四种配置的总体结果": "Overall results across four configurations", + "四种配置总览": "Four-configuration overview", + "四种配置 · 单次调用墙钟时间": "Four configurations · per-call wall time", + "全部配置 · 单次调用墙钟时间": "All configurations · per-call wall time", + "全部配置中单次均值最低": "Lowest mean per-call latency across all configurations", + "按模型分层 · 单次调用墙钟时间": "Within each model cohort · per-call wall time", + "同模型浏览器中单次均值最低": "Lowest per-call mean within this model cohort", + "同模型浏览器中通过数最高": "Highest pass count within this model cohort", + "同模型浏览器中通过率最高": "Highest success rate within this model cohort", + "只在同一模型的两个浏览器之间标记唯一较优值;相同值不标。跨模型批次不判优。": "A unique better value is marked only between browsers in the same model cohort. Ties are unmarked, and model cohorts are not ranked against each other.", + "四种配置均使用同一组 90 题,但两个模型层的运行日期和 browser‑eval 提交不同。": "All four configurations use the same 90 tasks, but the model cohorts ran on different dates and browser-eval commits.", + "每个动作按模型分层比较两个浏览器的单次调用墙钟均值,只标记层内唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "For each action, the two browsers are compared within each model cohort by mean per-call wall time, and only a unique cohort minimum is marked. Total time and call count depend on agent workload and provide context rather than a speed ranking.", + "每个动作把全部模型×浏览器配置放在一起比较单次调用墙钟均值,只标记全局唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "For each action, every model-by-browser configuration is compared together by mean per-call wall time, and only a unique global minimum is marked. Total time and call count depend on agent workload and provide context rather than a speed ranking.", + "四种配置中通过数最高": "Highest pass count across four configurations", + "四种配置中通过率最高": "Highest pass rate across four configurations", + "四种配置中单次均值最低": "Lowest mean per-call latency across four configurations", + "速度与浏览器资源": "Speed and browser resources", + "CPU 时间": "CPU time", + "平均 CPU 单核占用": "Mean single-core CPU utilization", + "时间加权平均内存": "Time-weighted mean memory", + "峰值内存": "Peak memory", + "网络接收量": "Network received", + "网络发送量": "Network transmitted", + "仅标记四种配置中数值唯一最低的一项。CPU 时间与网络总量累计得到逻辑结果所用的全部执行;内存来自 Browser Use 执行阶段的浏览器容器采样。": "Only a unique lowest value across the four configurations is marked. CPU time and network totals include every execution used by the logical result; memory comes from browser-container samples during Browser Use execution.", + "四种配置统一比较,只标记唯一最优值;相同值不标。通过数越高越好,耗时和 Tokens 越低越好。": "All four configurations are compared together, and only a unique best value is marked. Ties are unmarked. Higher pass counts are better; lower latency and token use are better.", + "四种完整配置统一比较,每个统计项只标记唯一最优值;相同值不标。这里比较模型 × 浏览器整套配置,不能把差值单独归因于浏览器。": "All four complete configurations are compared together, and only a unique best value is marked for each statistic; ties are unmarked. This compares complete model-by-browser configurations, so a difference cannot be attributed to the browser alone.", + "审查口径:": "Review scope:", + "438/438 次物理执行完成程序完整性核验,75/75 个最终失败完成逐条根因复核;成功结果没有逐条语义盲审,人工复核也没有改写官方 evaluator 分数。": "All 438 physical executions passed programmatic integrity review, and all 75 final failures received case-by-case root-cause review. Successful results did not receive case-by-case blinded semantic review, and human review did not rewrite official evaluator scores.", + "耗时和 Tokens 如何统计": "How latency and tokens are measured", + "Agent 链路累计耗时把取得逻辑结果所发生的执行相加,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。BrowserTool 累计耗时是所有 browser 工具调用的墙钟时间之和,包含工具封装、CDP 往返和页面等待。Tokens 同样累计正式结果包含的物理执行。": "Cumulative Agent-path latency sums the executions used to obtain the logical result. It includes sign-in, model inference, orchestration, page waits, BrowserTool calls, and cleanup, but excludes the preceding Docker site reset. Cumulative BrowserTool latency sums the wall time of all browser-tool calls, including tool-wrapper, CDP round-trip, and page-wait overhead. Tokens likewise sum the physical executions included in the official result.", + "结果分析": "Results analysis", + "完整 90 题结果入口": "Complete 90-task results entry", + "四种配置的通过率、耗时、Tokens 与浏览器资源总表已放在首页,避免在分析页重复。这里直接进入同题矩阵和逐结果失败诊断。": "The home page contains the consolidated pass-rate, latency, token, and browser-resource table for all four configurations. This page proceeds directly to the same-task matrix and per-result failure diagnosis.", + "查看首页核心指标": "View core metrics on the home page", + "四种配置先在同一张表和同一任务矩阵中比较;失败结果可直接打开诊断,查看发生位置、程序报错、复核根因、轨迹、画面和日志。": "The four configurations are compared in one summary table and one same-task matrix. Open any failed result to inspect its location, exact program error, reviewed root cause, trajectory, screenshots, and logs.", + "同题全配置对比": "Same-task comparison across all configurations", + "每行是一道任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is one task, with four model-by-browser configuration columns. Filter for differing outcomes, all-pass tasks, or all-fail tasks.", + "同题全配置矩阵": "Same-task all-configuration matrix", + "每行是同一任务,每列是一个模型×浏览器配置。新增配置只增加列。": "Each row is one task and each column is one model-by-browser configuration. New configurations add columns only.", + "关系": "Outcome pattern", + "结果有差异": "Outcomes differ", + "全部通过": "All pass", + "全部失败": "All fail", + "排除已证实外部干扰后的诊断子集": "Diagnostic subset after removing confirmed external interference", + "官方 90 题结果保持不变。为了看清浏览器和模型差异,下表暂时排除已核验为评测合同、站点、共享环境或参考状态问题的整道任务。": "The official 90-task results remain unchanged. To isolate browser and model differences, this table temporarily removes every task confirmed to have a task-contract, site, shared-environment, or reference-state problem.", + "失败诊断": "Failure diagnosis", + "发生位置": "Location", + "程序判定": "Program verdict", + "程序报错": "Program error", + "程序没有提供更具体的错误文本。": "The program did not provide a more specific error message.", + "复核结论": "Reviewed conclusion", + "复核结论:": "Reviewed conclusion:", + "负责范围": "Owning scope", + "失败动作": "Failed action", + "验收前最后一步": "Last step before evaluation", + "查看诊断": "Open diagnosis", + "查看期望与实际值": "View expected and actual values", + "查看页面画面": "View page screenshots", + "查看日志与完整证据": "View logs and complete evidence", + "查看完整日志": "View complete logs", + "关闭": "Close", + "尚未完成根因复核。": "Root-cause review is not complete.", + "当前尚无已核验根因。": "No verified root cause is available yet.", + "打开原始 JSON": "Open raw JSON", + "下载原始 JSON": "Download raw JSON", + "在页面中查看完整执行证据": "View complete execution evidence on this page", + "浏览器资源指标": "Browser resource metric", + "浏览器 CPU 时间": "Browser CPU time", + "结束时新增常驻内存": "Additional resident memory at completion", + "浏览器网络记录存在": "Browser network record exists", + "浏览器执行轨迹存在": "Browser execution trace exists", + "Agent 最终回答存在": "Agent final response exists", + "官方判分明细存在": "Official evaluation details exist", + "Agent 事件流存在": "Agent event stream exists", + "环境准备记录存在": "Environment preparation record exists", + "判分明细可读取": "Evaluation details are readable", + "最终回答可读取": "Final response is readable", + "最终回答与运行记录一致": "Final response matches the run record", + "报告分数与官方判分一致": "Reported score matches official evaluation", + "报告状态与官方判分一致": "Reported status matches official evaluation", + "官方 evaluator 版本已记录": "Official evaluator version is recorded", + "官方 evaluator 文件校验一致": "Official evaluator checksum matches", + "官方题集文件校验一致": "Official dataset checksum matches", + "模型请求次数已完整计入": "All model request attempts are accounted for", + "仅保留哈希与字节数(含登录态)": "Hash and byte count only (session-sensitive)", + "已脱敏公开副本": "Sanitized public copy", + "公开原文件": "Public original file", + "按证据清单处理": "Handled according to the evidence inventory", + "WebArena‑Verified 1.2.3 · CowAgent · 模型推理强度固定为低 · 官方程序化 evaluator": "WebArena-Verified 1.2.3 · CowAgent · model reasoning effort fixed to low · official programmatic evaluator", + "失败与干扰项": "Failures and interference", + "在四种配置中统一比较并只标记唯一最优值;相同值不标。通过数越高越好,耗时和 Tokens 越低越好。": "Values are compared across all four configurations, and only a unique best value is marked. Higher pass counts are better; lower latency and token usage are better.", + "查看全部配置的逐题矩阵 →": "View the task-by-task matrix for all configurations →", + "耗时统计包含什么": "What the latency statistics include", + "Agent 总耗时包含模型推理、页面等待和浏览器操作;BrowserTool 耗时是全部浏览器工具调用的墙钟时间之和,仍包含工具封装与 CDP 往返,不是纯内核 CPU 时间。": "Agent latency includes model inference, page waits, and browser operations. BrowserTool latency is the sum of wall-clock time across browser-tool calls; it still includes tool-wrapper and CDP round-trip overhead and is not pure browser-core CPU time.", + "Agent / 模型行为": "Agent / model behavior", + "全部 76 题的速度与浏览器资源": "Performance and browser resources across all 76 tasks", + "均值、P50、P95 都在四种完整配置中统一比较。CPU、内存和网络只计浏览器容器,不含 CowAgent 或模型服务;模型会改变动作数量和页面停留时间,因此这里比较整套配置,不能单独归因于浏览器内核。": "Mean, P50, and P95 are compared across all four complete configurations. CPU, memory, and network measurements cover only the browser container, not CowAgent or the model service. Because the model can change the action count and time spent on pages, these are whole-configuration measurements and cannot be attributed to the browser core alone.", + "每项只标记四种配置中数值唯一最低者。CPU 时间与网络总量累计逻辑结果使用的全部执行;内存来自 Browser Use 执行阶段的浏览器容器采样。": "Only a unique lowest value across the four configurations is marked for each statistic. CPU time and network totals include every execution used by a logical result; memory comes from browser-container samples during Browser Use execution.", + "浏览器证据接口": "Browser evidence interface", + "原因范围": "Cause scope", + "本轮处理": "Treatment in this run", + "修复与验证": "Fix and verification", + "保留": "Retain", + "保留并修复": "Retain and fix", + "排除并修复": "Exclude and fix", + "排除,修复已完成": "Exclude; fix completed", + "排除并重建": "Exclude and rebuild", + "属于当前被测组合;按轨迹定位检索、选择或提交错误。": "Part of the configuration under test; use the trajectory to locate retrieval, selection, or submission errors.", + "修正验收合同,通过独立预检后重跑四种配置。": "Correct the acceptance contract, pass an independent preflight, then rerun all four configurations.", + "修复依赖、会话或保存链路,通过读写预检后重跑。": "Fix the dependency, session, or save path, then rerun after read/write preflight checks.", + "这是浏览器集成能力的一部分;补齐响应正文或 Referer 采集后重跑。": "This is part of browser integration capability; complete response-body or Referer capture and rerun.", + "跨进程串行锁已经上线;受污染任务仍需重跑。": "The cross-process serialization lock is deployed; contaminated tasks still require reruns.", + "重新冻结参考状态,再重跑以区分 Agent 错误。": "Refreeze the reference state, then rerun to distinguish Agent errors.", + "参考状态与 Agent 混合": "Reference state mixed with Agent behavior", + "每个动作同时比较四种配置的单次调用墙钟均值,只标记唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "Each action compares mean per-call wall time across all four configurations and marks only a unique minimum. Total time and call count depend on Agent workload, so they explain workload but are not ranked.", + "默认显示失败。点击“查看诊断”可先在当前页核对发生位置、程序报错、复核根因和处理方案,再进入完整轨迹、画面与日志。": "Failures are shown by default. Select Open diagnosis to inspect the location, exact program error, reviewed root cause, and treatment before opening the full trajectory, screenshots, and logs.", + "该结果通过全部官方程序化验收;下方可核对任务合同、执行轨迹和运行证据。": "This result passed every official programmatic evaluator. The task contract, execution trajectory, and runtime evidence are available below.", + "失败位置": "Failure stage", + "人工复核根因": "Reviewed root cause", + "页面画面": "Page frames", + "查看 evaluator 断言": "View evaluator assertions", + "查看失败附近轨迹": "View trajectory near the failure", + "查看最后页面画面": "View final page frames", + "下载执行日志": "Download execution log", + "核验依据:": "Review evidence:", + "对比口径:": "Comparison treatment:", + "该结果已确认含非模型/浏览器干扰,官方分数保留,但暂不纳入干净子集。": "This result contains confirmed interference outside the model/browser variables. Its official score is retained, but it is temporarily excluded from the clean subset.", + "失败执行的最后页面画面": "Final page frames from the failed execution", + "下图是判分权威执行的最后页面画面,用于核对失败前的可见状态;它们不一定正好对应错误发生的瞬间。": "These are the final page frames from the scoring execution. They help verify visible state before failure but may not capture the exact instant the error occurred.", + "是否属于目标变量": "Target variable?", + "处理": "Treatment", + "运行配置": "Run configuration", + "复核根因": "Reviewed root cause", + "负责对象与处理": "Owner and treatment", + "证据": "Evidence", + "诊断": "Diagnosis", + "画面": "Frames", + "日志": "Logs", + "详情": "Details", + "查看该浏览器的全部结果": "View all results for this browser", + "完整结果只保留一份。下方入口打开结果分析页,并预先筛选当前浏览器;失败诊断、轨迹、页面画面与日志均从该清单进入。": "The report keeps one result inventory. The link below opens it with this browser preselected; failure diagnoses, trajectories, page frames, and logs are all reached from that inventory.", + "打开筛选后的结果清单 →": "Open the filtered result inventory →" + }, + "TEXT_6": { + "90 题跑完了,但结论不能只看 73、71、70": "Ninety tasks are complete, but 73, 71, and 70 do not tell the whole story", + "90 题、四种配置:相近成绩背后的不同原因": "Ninety tasks and four configurations: different causes behind similar scores", + "四种配置的官方分数很接近;75 个零分经复核后分散在多个责任层,不能统一归给 Agent 或浏览器。": "The four official scores are close. Review placed the 75 zeros across several owning layers rather than assigning them all to the agent or browser.", + "WebArena‑Verified 1.2.3 · CowAgent · reasoning=low · 官方程序化 evaluator": "WebArena-Verified 1.2.3 · CowAgent · reasoning=low · official programmatic evaluator", + "独立阅读版": "Standalone reading edition", + "返回交互报告": "Return to the interactive report", + "论点与证据数据": "Claims and evidence data", + "GPT‑6 Astra 没有在首次运行中输给 GPT‑5.6 Sol": "GPT-6 Astra did not trail GPT-5.6 Sol on the first attempt", + "Astra 首次通过 71/90,Sol 为 70/90": "Astra passed 71/90 on the first attempt; Sol passed 70/90", + "Sol × Chromium 有 3 个重试任务恢复成功,最终到 73/90。Astra 的 71/90 并非首次表现落后。": "Three Sol × Chromium tasks recovered on retry, raising its final score to 73/90. Astra's 71/90 does not reflect weaker first-attempt performance.", + "两层模型共有 9 个单边通过配对": "Nine task pairs were one-sided across the two model cohorts", + "14 题含已确认的外部干扰": "Fourteen tasks contain confirmed external interference", + "干扰来自验收合同、站点、共享环境或参考状态。官方分数保留,另用 76 题事后诊断子集观察目标变量。": "The interference comes from acceptance contracts, sites, shared state, or reference state. Official scores remain unchanged; a post-hoc 76-task subset is used only for diagnosis.", + "Astra 在两个浏览器上都是 71/90;Sol 都是 70/90。Sol × Chromium 有 3 个重试任务恢复成功,最终才到 73/90。": "Astra scored 71/90 with both browsers on the first attempt; Sol scored 70/90. Sol × Chromium reached 73/90 only after three failed tasks recovered on retry.", + "Moli 与 Chromium 的总数接近,不代表执行等价": "Similar totals for Moli and Chromium do not mean equivalent execution", + "Astra 层有 69 题共同通过、2 题各自单边通过;Sol 层是 69 题共同通过、4 题仅 Chromium 通过、1 题仅 Moli 通过。": "Under Astra, both browsers passed 69 tasks and each passed two tasks that the other failed. Under Sol, both passed 69, Chromium alone passed four, and Moli alone passed one.", + "官方 90 题成绩不是干净的模型或内核排名": "The official 90-task score is not a clean model or browser-core ranking", + "14 题已证实受验收合同、站点、共享环境或参考状态干扰。官方分数保留,另用 76 题事后诊断子集观察目标变量。": "Fourteen tasks have confirmed interference from acceptance contracts, sites, shared state, or reference state. Their official scores remain unchanged; a post-hoc 76-task subset is shown only for diagnosis.", + "先把分数读对": "Read the score correctly first", + "首次成功与重试恢复": "First-attempt success and retry recovery", + "报告的主成绩按预先固定的规则取最后一次执行,失败后最多重试一次。因此首次通过率回答“第一次能否完成”,最终通过率还包含“失败后能否恢复”。": "The report's official score uses the last execution under the preregistered rule, with at most one retry after failure. First-attempt success answers whether a task worked immediately; final success also reflects recovery after failure.", + "首次 70/90": "First attempt 70/90", + "首次 71/90": "First attempt 71/90", + "首次通过 70/90": "First-attempt passes: 70/90", + "首次通过 71/90": "First-attempt passes: 71/90", + "失败后重试 20 题": "20 failed tasks retried", + "失败后重试 19 题": "19 failed tasks retried", + "恢复 +3 题": "3 tasks recovered", + "恢复 0 题": "No task recovered", + "首次通过": "First-attempt passes", + "重试任务": "Retried tasks", + "重试恢复": "Recovered on retry", + "官方最终通过": "Official final passes", + "为什么看起来 Astra 更低?": "Why does Astra appear lower?", + "两个模型层在不同日期、不同 browser‑eval 提交上运行(GPT-5.6 Sol 51a37526、GPT-6 Astra bed8469e)。任务、CowAgent 提交、浏览器镜像与 evaluator 数据相同,但这仍不是严格的模型 A/B。": "The model cohorts ran on different dates and browser-eval commits (GPT-5.6 Sol 51a37526; GPT-6 Astra bed8469e). They share tasks, CowAgent commit, browser images, and evaluator data, but this is still not a strict model A/B test.", + "总数相近,不是同一批任务相近": "Similar totals can hide different task outcomes", + "按同题比较浏览器": "Compare browsers task by task", + "浏览器对比必须留在同一模型层内。汇总数会隐藏互换的成败任务;下表给出同题对照。": "Browser comparisons must stay within a model cohort. Aggregate totals hide swapped outcomes; the table gives the paired view.", + "浏览器对比必须留在同一模型层内。它们各自的总数会隐藏互换的成败任务;下表才是同题对照。": "Browser comparisons must stay within a model cohort. Aggregate totals hide tasks that swap outcomes; the paired table shows the relevant comparison.", + "每行只比较同一模型下的 90 个配对任务。": "Each row compares the 90 paired tasks within one model cohort.", + "两个模型层合计有 9 个单边通过,可用于定位兼容性问题;样本量和方向都不支持总体优劣判断。": "Across both model cohorts, nine task pairs were one-sided. They help locate compatibility defects, while the sample size and direction do not support an overall ranking.", + "查看 90 题的四配置矩阵 →": "View the four-configuration matrix for all 90 tasks →", + "错误到底发生在哪里": "Where the failures occurred", + "程序判分落点与复核根因": "Programmatic failure location and reviewed root cause", + "程序判分先说明哪类规则未通过;人工复核再沿最终回答、evaluator 断言、HAR 和 Agent 轨迹追到直接原因。这两个层次需要分开。": "Programmatic scoring first identifies which rule class failed. Human review then follows the final answer, evaluator assertions, HAR, and agent trajectory to the direct cause. These are separate layers.", + "程序判分落点": "Programmatic failure location", + "只有网络副作用规则未通过": "Only the network-side-effect rule failed", + "evaluator 没找到合同要求的请求或可观察副作用": "The evaluator did not find the required request or observable side effect", + "只有最终回答规则未通过": "Only the final-answer rule failed", + "最终回答与结构化答案合同不一致": "The final answer disagreed with the structured answer contract", + "两类规则同时未通过": "Both rule classes failed", + "最终回答和网络副作用均未满足合同": "Neither the final answer nor the network side effect satisfied the contract", + "官方 evaluator 的失败规则组合": "Failed rule combinations from the official evaluator", + "“网络副作用未通过”只描述程序判分位置。它可能源于 Agent 没有提交,也可能源于 evaluator、站点或 HAR 采集问题。": "A failed network-side-effect rule identifies the scoring location only. It can result from an agent not submitting, or from the evaluator, site, or HAR capture.", + "复核根因": "Reviewed root cause", + "75 个失败结果的复核原因分布": "Reviewed cause distribution for 75 failed results", + "75 个官方失败结果都读取了最终回答、evaluator 断言、HAR 和 Agent 轨迹。分类统计不是第二套打分,而是回答“这个 0 分由哪一层直接产生”。": "All 75 official failures were reviewed against the final answer, evaluator assertions, HAR, and agent trajectory. The categories do not rescore tasks; they identify the layer that directly produced each zero.", + "检索不全、选错对象、字段遗漏或流程提前结束": "Incomplete retrieval, wrong selection, omitted fields, or premature termination", + "验收合同与页面语义不一致,或 evaluator 自身报错": "Acceptance contracts disagree with page semantics, or the evaluator itself errors", + "搜索、会话或保存依赖故障": "Search, session, or persistence dependency failure", + "HAR 缺少 Referer、响应正文或出现重复记录": "HAR lacks Referer or response bodies, or contains duplicate records", + "共享环境": "Shared environment", + "并发执行改写了任务初始状态": "Concurrent execution changed the task's initial state", + "参考漂移与 Agent 遗漏同时存在": "Reference drift and an agent omission both contributed", + "Agent / 模型行为": "Agent / model behavior", + "保留并修复": "Retain and fix", + "Agent 行为": "Agent behavior", + "例如漏掉商品、选错最优候选、写错字段,或在 40 步前提前结束。这些属于当前 Agent × 模型配置的真实失败。": "Examples include missing products, choosing the wrong candidate, writing the wrong field, or stopping before the 40-step limit. These are genuine failures of the current agent-model configuration.", + "验收规则": "Acceptance rules", + "站点与环境": "Sites and environment", + "浏览器证据": "Browser evidence", + "的 evaluator 在比较实际提交前就因单元素 Alternatives 配置抛错;": " fails before comparing the actual submission because the evaluator throws on a one-item Alternatives configuration; ", + "要求“不提交”,验收却要求 POST。这些 0 分不能归给模型或浏览器。": " says not to submit, while its evaluator requires a POST. These zeros cannot be attributed to the model or browser.", + "Magento 搜索或库存保存服务失效,以及": "Magento search or stock-save services failed, and ", + "的并发状态污染,都会让正确操作无法通过。": " suffered shared-state contamination. Either can prevent correct work from passing.", + "Moli 的部分 HAR 没有 Referer 或响应正文,例如": "Some Moli HAR records omit Referer or response bodies, including ", + "。页面可能已经操作成功,但 evaluator 无法从网络记录确认;这是浏览器集成层的有效信号。": ". The UI operation may have succeeded, but the evaluator cannot confirm it from network evidence. This is a valid browser-integration signal.", + "Moli 的部分 HAR 没有 Referer 或响应正文。页面可能已经操作成功,但 evaluator 无法从网络记录确认;这是浏览器集成层的有效信号。": "Some Moli HAR records omit Referer or response bodies. The UI operation may have succeeded, but the evaluator cannot confirm it from network evidence. This is a valid browser-integration signal.", + "官方成绩与诊断子集分开看": "Keep official scores separate from the diagnostic subset", + "历史官方分数不人工改写。对已证实受任务 / evaluator、站点、共享环境或参考状态干扰的 14 题,诊断时按整题排除四种配置,剩余 76 题。": "Historical official scores are never manually rewritten. For diagnosis, 14 tasks with confirmed task/evaluator, site, shared-state, or reference-state interference are removed across all four configurations, leaving 76 tasks.", + "76 题通过": "Passes on 76 tasks", + "诊断通过率": "Diagnostic success rate", + "这是事后诊断口径,用于确定修复顺序,不是新的官方分数。只有修复后重跑完整四配置,才能产生新成绩。": "This post-hoc view prioritizes fixes; it is not a replacement official score. A new score requires rerunning all four configurations after the fixes.", + "这份结果审查到了什么": "What the review established", + "审查覆盖与边界": "Review coverage and boundaries", + "438/438 次物理执行检查了 HAR、Agent trace、最终回答、evaluator 输出和环境准备回执。": "All 438 physical executions were checked for HAR, agent trace, final answer, evaluator output, and environment-preparation receipts.", + "438/438 个证据包由程序核对文件存在性、哈希、JSON 可读性、回答 / 分数 / 状态一致性和运行身份。": "Programs checked all 438 evidence bundles for file presence, hashes, JSON readability, response/score/status consistency, and runtime identity.", + "官方程序验收": "Official programmatic evaluation", + "每题按最终回答和必要网络副作用判分;没有 LLM judge。": "Each task is scored from its final answer and required network side effects; no LLM judge is used.", + "失败根因复核": "Failure root-cause review", + "75/75 个最终失败结果逐条查看错误、断言、轨迹与网络记录,归类并给出处理边界。": "All 75 final failures were reviewed against errors, assertions, trajectories, and network records, then classified with a defined treatment boundary.", + "75/75 个最终失败结果逐条阅读错误、断言、最终回答、HAR 和 Agent 轨迹,归类并给出处理边界。": "All 75 final failures received case-by-case review of errors, assertions, final answers, HAR, and agent trajectories, followed by classification and treatment boundaries.", + "审查分为完整性检查和失败根因复核。": "The review has two layers: integrity checks and failure root-cause review.", + "查看核验记录 →": "View review records →", + "性能与资源:只在四种配置首次都成功的任务上看": "Performance and resources on tasks all four configurations passed on the first attempt", + "共同成功任务的性能与资源": "Performance and resources on common final successes", + "当前没有四种配置最终都成功的任务,资源差异暂不比较。": "No task was finally passed by all four configurations, so resource differences are unavailable.", + "首次共同成功任务的资源差异": "Resource differences on common first-attempt successes", + "当前没有四种配置首次都成功的任务,资源差异暂不比较。": "No task was passed on the first attempt by all four configurations, so resource differences are unavailable.", + "Agent 链路还包含模型推理和页面等待,因此不能解读为纯内核性能。": "The agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", + "查看口径与四配置明细 →": "View the cohort definition and four-configuration details →", + "Moli 的内存区间更低": "Moli has the lower memory range", + "Chromium 的工具耗时与 CPU 更低": "Chromium has lower tool latency and CPU time", + "下表限定为首次执行共同成功的 64 题,每个配置只取第一次执行并报告 P50。这既排除了重试累计,也避免把“很快失败”错当成速度优势。": "The table uses the 64 tasks that all four configurations passed on the first attempt. It reports P50 from the first execution only, excluding retry accumulation and the false speed advantage of fast failures.", + "峰值内存 P50": "Peak memory P50", + "CPU 时间 P50": "CPU time P50", + "Moli 的优势很明确:内存": "Moli's clear advantage is memory", + "Moli:峰值内存更低": "Moli: lower peak memory", + "这批首次共同成功任务上,Moli 的峰值内存 P50 约为 190 MiB,Chromium 约为 500–567 MiB。这个差距在两个模型层都出现。": "On these common first-attempt successes, Moli's P50 peak memory is about 190 MiB versus roughly 500-567 MiB for Chromium. The gap appears in both model cohorts.", + "代价也很明确:工具耗时和 CPU": "The tradeoff is tool latency and CPU time", + "Chromium:工具耗时与 CPU 更低": "Chromium: lower tool latency and CPU time", + "同一批任务上,Chromium 的 BrowserTool P50 和 CPU 时间均低于 Moli。整条 Agent 链路还包含模型推理和页面等待,不能解读为纯内核性能。": "On the same tasks, Chromium has lower BrowserTool P50 and CPU time than Moli. The full agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", + "同一批任务上,Moli 的 BrowserTool P50 和 CPU 时间都高于 Chromium。整条 Agent 链路还包含模型推理和页面等待,不能解读为纯内核性能。": "On the same tasks, Moli has higher BrowserTool P50 and CPU time than Chromium. The full agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", + "最后怎么读这份报告": "The practical reading", + "工程结论": "Engineering conclusions", + "官方 90 题成绩显示,四种端到端配置都完成了大约八成任务,浏览器之间的单边差异集中在少数任务。75 个零分分布在 Agent、验收规则、站点、浏览器证据接口和共享环境等多层。": "The official 90-task scores show that all four end-to-end configurations complete about four-fifths of the sample, with one-sided browser differences concentrated in a few tasks. The 75 zeros span agent behavior, acceptance rules, sites, browser-evidence interfaces, and shared state.", + "官方 90 题成绩说明,四种端到端配置都完成了大约八成任务,浏览器之间的单边差异集中在少数任务。错误复核又证明,不能把 75 个零分全部归给 Agent 或浏览器。": "The official 90-task scores show that all four end-to-end configurations complete about four-fifths of the sample and that one-sided browser differences concentrate in a few tasks. Failure review also shows that the 75 zeros do not all belong to the agent or browser.", + "对工程选型有用的结论是:Moli 的内存占用明显更低,但当前 HAR 完整性、工具耗时和部分任务兼容性还需修复;Chromium 在 76 题诊断子集中两个模型层都多通过任务,但当前只有一个正式样本,不能外推成总体优势。": "For engineering decisions, Moli uses substantially less memory, while HAR completeness, tool latency, and some task compatibility still need work. Chromium passes more tasks in both model cohorts on the 76-task diagnostic subset, but one formal sample cannot establish a general advantage.", + "下一轮只做两件事": "Two priorities for the next run", + "下一轮验证": "Next validation run", + "上游 PR #45": "upstream PR #45", + "先修复并重跑 14 题干扰项,再对同一冻结代码的四种配置增加重复次数。在此之前,这份报告用于定位兼容性和证据链问题,不发布模型或浏览器排名。": "First fix and rerun the 14 interference tasks; then add repetitions for all four configurations on one frozen codebase. Until then, this report is for locating compatibility and evidence-chain defects, not for publishing a model or browser ranking.", + "先修复并重跑 14 题干扰项,再对同一冻结代码的四种配置增加重复次数。在此之前,这份报告适合找兼容性和证据链问题,不适合发布模型或浏览器排名。": "First fix and rerun the 14 interference tasks; then add repetitions for all four configurations on one frozen codebase. Until then, this report is useful for locating compatibility and evidence-chain defects, not for publishing a model or browser ranking.", + "查看全部配置的逐题矩阵 →": "View the per-task matrix for all configurations →", + "耗时统计包含什么": "What the latency metrics include", + "Agent 总耗时包含模型推理、页面等待和浏览器操作;BrowserTool 耗时是全部浏览器工具调用的墙钟时间之和,仍包含工具封装与 CDP 往返,不是纯内核 CPU 时间。": "Agent total time includes model inference, page waits, and browser actions. BrowserTool time is the summed wall time of all browser-tool calls; it still includes tool-wrapper and CDP round-trip overhead and is not pure browser-core CPU time.", + "把取得逻辑结果所发生的执行相加,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": " sums the executions used to obtain the logical result, including sign-in, model inference, orchestration, page waits, BrowserTool calls, and cleanup, but excluding the preceding Docker site reset. ", + "是所有 browser 工具调用的墙钟时间之和,包含工具封装、CDP 往返和页面等待。Tokens 同样累计正式结果包含的物理执行。": " is the sum of wall time across all browser-tool calls, including tool-wrapper, CDP round-trip, and page-wait overhead. Tokens likewise sum the physical executions included in the official result.", + "Browser Use 能力评测": "Browser Use capability evaluation", + "首次结果与重试恢复分开统计": "Separate first-attempt results from retry recovery", + "最终通过": "Final passes", + "查看评测集": "View dataset", + "下钻全部结果": "Inspect all results", + "下载论点与证据": "Download claims and evidence", + "正式比较与原始记录分开看": "Separate the formal comparison from raw records", + "每行是一道审查合格任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is a review-qualified task, with one column per model-browser configuration. Filters isolate divergent, all-pass, or all-fail outcomes.", + "这里保留预注册 90 题的全部原始结果,包括退出正式比较的 14 题。默认显示失败;点击“查看诊断”可核对发生位置、程序报错、复核根因、轨迹、画面和日志。": "This inventory retains raw results for all 90 preregistered tasks, including the 14 excluded from the formal comparison. Failures are shown by default; open a diagnosis to inspect the failure point, program error, reviewed cause, trajectory, frames, and logs." + } +} diff --git a/harness/browser_use/report.py b/harness/browser_use/report.py index ebcdfbeab..e45b2c331 100644 --- a/harness/browser_use/report.py +++ b/harness/browser_use/report.py @@ -20,11 +20,10 @@ from harness.record_io import load_jsonl from harness.report_languages import write_language_contract +from harness.reporting.assets import bound_json_asset +from harness.reporting.templates import template_identity from harness.web_fetch.report_style import STYLE as SHARED_STYLE -from .failure_cause_review import ( - DEFAULT_REVIEW_PATH as FAILURE_CAUSE_REVIEW_PATH, -) from .failure_cause_review import ( LAYERED_REVIEW_SCHEMA, extract_failure_cause_review_cohort, @@ -32,6 +31,9 @@ localized_cause, validate_failure_cause_review, ) +from .failure_cause_review import ( + review_path as failure_review_path, +) from .failure_cause_review import ( sha256 as file_sha256, ) @@ -45,9 +47,9 @@ trajectory, trajectory_label, ) -from .selection import PilotSelection, load_pilot, validate_against_official +from .selection import PilotSelection, load_pilot, report_selection_path, validate_against_official -ENGINE_LABELS = {"chromium": "Chromium", "moli": "Moli 1.1.3"} +ENGINE_LABELS = {"chromium": "Chromium", "moli": "Moli"} MODEL_LABELS = {"gpt-5.6-sol": "GPT-5.6 Sol"} BASELINE_ENGINE = "chromium" TIER_LABELS = {"easy": "简单", "medium": "中等", "hard": "困难"} @@ -57,8 +59,6 @@ "reddit": "Reddit", "gitlab": "GitLab", } -MUTATE_IDS = {399, 404, 441, 506} -NAVIGATE_IDS = {44, 45, 118} RESEARCH_ROOT = Path(__file__).parents[2] / "research" @@ -289,7 +289,7 @@ def _table( table_id: str = "", classes: str = "", ) -> str: - head = "".join(f"{_e(x)}" for x in headers) + head = "".join(f"{x if isinstance(x, Safe) else _e(x)}" for x in headers) body = "".join( "" + "".join(f"{x if isinstance(x, Safe) else _e(x)}" for x in row) + "" for row in rows @@ -301,12 +301,6 @@ def _table( return f'
{cap}{head}{body}
' -def _task_type(task_id: int) -> str: - return ( - "MUTATE" if task_id in MUTATE_IDS else "NAVIGATE" if task_id in NAVIGATE_IDS else "RETRIEVE" - ) - - def _attempt_id(row: dict[str, Any]) -> str: model = str(row.get("model") or (next(iter(MODEL_LABELS)) if len(MODEL_LABELS) == 1 else "")) if not model: @@ -663,7 +657,8 @@ def _paired_summary(rows: list[dict[str, Any]]) -> dict[str, Any]: def _resolve_tasks( - selection: PilotSelection, *, expected_data_checksum: str | None = None + selection: PilotSelection, *, expected_data_checksum: str | None = None, + dataset_path: Path | None = None ) -> list[dict[str, Any]]: try: from harness.environments.webarena.runtime import ( @@ -671,6 +666,12 @@ def _resolve_tasks( ) benchmark = configure_benchmark() + if dataset_path is not None: + from webarena_verified import WebArenaVerified + if dataset_path.is_symlink() or not dataset_path.is_file(): + raise ValueError("Frozen official dataset is missing or symlinked") + benchmark = WebArenaVerified(config=benchmark.config.model_copy( + update={"test_data_file": dataset_path})) if expected_data_checksum: dataset_path = Path(benchmark.config.test_data_file) actual_data_checksum = _sha256(dataset_path) @@ -713,7 +714,7 @@ def _resolve_tasks( raise ValueError(f"official task {task['task_id']} has no evaluator contract") expected = (evals[0].get("expected") or {}) if evals else {} task["task_type"] = str( - expected.get("task_type") or _task_type(int(task["task_id"])) + expected.get("task_type") or "UNKNOWN" ).upper() return tasks @@ -801,7 +802,7 @@ def _failure_cause_table(verified_causes: dict[str, dict[str, Any]]) -> str: "benchmark": ( "任务 / evaluator", "排除并修复", - "修正验收合同,通过独立预检后重跑四种配置。", + "修正验收合同,通过独立预检后重跑全部参评配置。", ), "site": ("自托管站点", "排除并修复", "修复依赖、会话或保存链路,通过读写预检后重跑。"), "evidence": ( @@ -883,7 +884,7 @@ def _clean_subset_table( table_id="clean-subset-table", classes="core-comparison-table", ) - + '

这是事后诊断口径,不是官方新分数。它按任务统一剔除任一配置已证实受任务/evaluator、站点、共享环境或参考状态干扰的任务,保证四个配置分母一致。

' + + '

这是事后诊断口径,不是官方新分数。它按任务统一剔除任一配置已证实受任务/evaluator、站点、共享环境或参考状态干扰的任务,保证各配置分母一致。

' f'

本轮剔除 {len(excluded)} 题:{_e("、".join(f"#{task_id}" for task_id in sorted(excluded)))}。

' ) @@ -1127,12 +1128,12 @@ def _summary_comparison_rows(summary: dict[str, dict[str, dict[str, Any]]]) -> l _winner( f"{current['passed']}/{current['completed']}", won=key in higher_metrics["passed"], - label="四种配置中通过数最高", + label="全部配置中通过数最高", ), _winner( _pct(current["score"]), won=key in higher_metrics["score"], - label="四种配置中通过率最高", + label="全部配置中通过率最高", ), _metric_stack( [ @@ -1481,7 +1482,7 @@ def _english_page(body: str) -> str: def _hero(eyebrow: str, title: str, lead: str) -> str: - return f'
{_e(eyebrow)}

{_e(title)}

{_e(lead)}

' + return f'
{_e(eyebrow)}

{_e(title)}

{lead if isinstance(lead, Safe) else _e(lead)}

' def _section(title: str, body: str) -> str: @@ -2252,84 +2253,54 @@ def _replay_original_contract_rows( def _trajectory_replay_control( bridge: dict[str, Any] | None, attempts: list[dict[str, Any]] | None = None ) -> str: + """Explain the selected replay comparison without inheriting a past article.""" if bridge is None: return "" - result = bridge["effective_results"] + from .report_i18n import bilingual_text + + def text(zh, en): + return Safe(bilingual_text(zh, en)) + + result_rows = [ + [str(value.get("label") or key.capitalize()), f"{value['passes']}/{value['tasks']}"] + for key, value in bridge["effective_results"].items() + if isinstance(value, dict) and 'tasks' in value and 'passes' in value + ] provenance = bridge["chromium_trajectory_provenance"] - origins = provenance["successful_browser_use_execution_by_origin"] - diagnostic = "" + source_labels = { + "authoritative_successful_browser_use_execution": text("历史成功轨迹", "Historical successful executions"), + "expert_repair_spec": text("专家修复轨迹", "Expert-repaired trajectories"), + } + source_rows = [ + [source_labels["authoritative_successful_browser_use_execution"], provenance["successful_browser_use_execution"]], + [source_labels["expert_repair_spec"], provenance["expert_repair_spec"]], + *[[Safe(f'{_e(origin.replace("_", " × "))}'), count] + for origin, count in provenance["successful_browser_use_execution_by_origin"].items()], + ] + contract_rows = [ + [source_labels.get(source, source.replace("_", " ")), counts["official_task_contract"], counts["corrected_replay_contract"]] + for source, counts in bridge["source_contract_counts"].items() + ] + body = '

' + text( + "固定轨迹控制", "Fixed-trajectory control") + '

' + text( + "轨迹重放执行固定动作,验收使用预录回答(recorded_response)和本次网络记录(HAR)。它不测 Agent 的自主规划;回答通过也不证明本次读到了目标数据。要求网络事件的合同仍检查本次 HAR。", + "Replay executes fixed actions and evaluates the recorded_response and current HAR. It does not measure autonomous Agent planning; an accepted recorded answer does not establish a fresh reading. Contracts requiring network events still inspect the current HAR.") + '

' + body += _table([text("配置", "Configuration"), text("通过数/任务数", "Passes / tasks")], result_rows) + body += '

' + text("轨迹来源", "Trajectory provenance") + '

' + body += _table([text("来源", "Source"), text("任务数", "Tasks")], source_rows) + body += '

' + text("轨迹来源与验收合同", "Trajectory source and acceptance contract") + '

' + body += _table([text("来源", "Source"), text("官方原合同", "Original official contract"), text("修订合同", "Revised contract")], contract_rows) if attempts is not None: - diagnostic_rows = "".join( - "" + "".join(f"{_e(cell)}" for cell in row) + "" - for row in _replay_original_contract_rows(bridge, attempts) - ) - diagnostic = ( - '

保留原合同的任务仍有自主执行失败

' - '

下表固定排除修订合同任务,按轨迹来源统计自主执行的官方通过数。' - '这是事后诊断,主报告分母仍为 90;历史成功路径经过筛选,不能把此表当成新的能力排名。

' - '
' - '' - + diagnostic_rows + '
配置全部原合同任务历史成功路径来源专家修复路径来源
' - ) - contract_rows = "".join( - f'{label}{counts["official_task_contract"]}{counts["corrected_replay_contract"]}' - for source, label in (("authoritative_successful_browser_use_execution", "历史成功轨迹"), ("expert_repair_spec", "专家修复轨迹")) - for counts in [bridge["source_contract_counts"][source]] - ) - correction_rows = "".join( - f'{label}{", ".join(map(str, bridge["correction_categories"][key]))}{description}' - for key, label, description in ( - ("event_selection", "仅修改事件选择", "按匹配条件选择保存请求,避免随后请求覆盖。"), - ("event_selection_and_expected", "事件选择与预期字段修改", "修订路由、请求字段或 Referer 条件,并匹配所需异步请求。"), - ("reference_or_acceptance", "参考答案或验收要求修改", "涉及商品名、表单字段、商品选择规则、未提交表单状态及冗余保存要求。"), - ) - ) - return ( - '

固定轨迹控制:它验证浏览器执行,不测自主规划

' - '

Chromium 的 90/90 不是 CowAgent 的 90/90。轨迹重放把既定 BrowserTool 调用交给浏览器,模型调用数为 0;' - 'Chromium 的 90/90 表示这 90 组固定动作、预录回答与绑定验收合同通过了重放验收,其中部分合同经过修订。它不让 Agent 在当次运行中观察、检索、比较、选择或决定何时结束。

' - '

验收使用轨迹预录回答(recorded_response)与本次网络记录(HAR)。对于检索问答题,通过不证明当前浏览器读到了所需数据,也不证明 Agent 在本次运行中计算出了答案;要求网络事件的合同仍会检查本次 HAR 是否包含对应请求。

' - '

90 题中的 37 道检索题均只使用回答验收,不要求网络事件:36 题沿用原合同,Task 279 使用修订合同。这 37 题的本轮读数与答案尚未验证;原评分保留,其余 53 题也不能替代原 90 题成为新的排名。

' - '
Chromium:90/90

固定动作、预录回答与绑定合同的重放验收结果。

' - f'
Moli:{result["moli"]["passes"]}/{result["moli"]["tasks"]}

同一固定路径仍有 36 条失败,不能把浏览器差异归零。

' - f'
路径来源:{provenance["expert_repair_spec"]} + {provenance["successful_browser_use_execution"]}

30 条专家修复路径,加 60 条历史成功的 Browser Use 路径。

' - '

后 60 条在进入重放集之前已经按“成功”条件筛选,其中 ' - f'{origins["gpt-5.6-sol_chromium"]} 条来自 Sol × Chromium、{origins["gpt-5.6-sol_moli"]} 条来自 Sol × Moli、' - f'{origins["gpt-6-astra_chromium"]} 条来自 Astra × Chromium。' - '因此固定轨迹成绩受给定路径、预录回答和验收合同共同约束,不能作为 Agent 决策能力、模型能力或完整 90 题自主通过率的反事实估计。

' - '

轨迹来源与验收合同

' - f'

90 条轨迹中,{bridge["contract_counts"]["official_task_contract"]} 条保留官方原合同,{bridge["contract_counts"]["corrected_replay_contract"]} 条使用修订合同。专家修复轨迹不一定修改验收;历史成功轨迹也可能修订验收。合同差异与路径来源需同时考虑。

' - '
' - + contract_rows + '
轨迹来源官方原合同修订合同
' - '
' - + correction_rows + '
修订类别任务验收差异
' - + diagnostic + - '

历史复核曾将 Moli 的 36 个固定轨迹失败归为浏览器责任;新发现的视口设置缺陷要求重新核对这一归因。' - '历史 Browser Use 的 Chromium 实际宽度为 780,Moli 为 1920;Task 173 的搜索链接在宽度达到 992 时由网页 CSS 隐藏。' - '该任务的失败不能单凭可见性超时归因于浏览器。历史分数保留,统一视口后的比较须重新冻结完整任务范围与轨迹资格。

' - '

数据绑定:跨轨迹控制摘要;' - '36 个 Moli 固定轨迹失败的逐题复核。

' - '

从失败结果追到决策与运行设置

' - '

Task 676 · Sol × Chromium · 验收证据:' - '官方合同要求带 filters[status]=fraud 的订单列表 XHR。HAR 已记录该请求、200 状态和正确 Referer;' - '操作轨迹显示 Suspected Fraud 已生效。' - 'NetworkEventEvaluator._filter_events_by_criteria 却对 NAVIGATE + GET 只保留最后的文档导航,遗漏 XHR;' - '默认 last_event_only 又可能在同路径请求中选中后来的 notification_area。两个模型的 Chromium 结果都受此影响。' - 'Moli 的同题证据还缺少 XHR Referer。' - '重放修订既改变事件选择,也移除了 Referer 条件;后者并非 Chromium 这次误判的原因。四个配置的原始失败分数保持不变。

' - '

Task 124 · Astra × Chromium:' - 'Agent 在第 9 个决策就结束,未耗尽 40 步。轨迹把 wireless earphone 查询扩大成 earphone,' - '工具结果仍显示 Items 1 to 12 of 24 total,最终却提交 min=0.01、max=287.64。' - '失败发生在候选资格与检索完整性判断;轨迹中有可读结果,不能用浏览器不可操作或步骤不足解释。' - '当前运行器依赖模型执行提示中的完成自检,没有独立验证候选全集。提示要求自检并不等于已经完成自检。

' - '

Task 510 · Sol × Moli:' - '第 40 个决策仍在调用工具,最终回答承认尚未结账。' - 'harness/browser_use/runner.py 的 _exhausted_agent_steps 检测这一状态,并通过 _max_steps_response 标记未完成。' - '这证明预算上限中断了流程,但所选 Hiquphon Tweeters 本身也不符合官方目标;增加预算能否完成正确购买仍需对照实验。

' - '

两模型均使用 reasoning_effort=low 和 40 步上限。' - 'harness/browser_use/model.py 限定 low,runner.py 在创建适配器时也传入 low。' - '现有分数只反映这套 Agent、工具与推理设置;尚无固定观察输入、仅改变推理档位的实验,不能把分数接近解释为模型能力相同。

' - ) + body += '

' + text( + "以下为保留原合同任务的事后诊断,不替代自主执行的完整任务分母或官方结果。", + "The following post-hoc diagnostic uses original-contract tasks. It does not replace the full autonomous-run population or official outcomes.") + '

' + body += _table([text("配置", "Configuration"), text("全部原合同任务", "All original-contract tasks"), text("历史成功路径来源", "Historical-success paths"), text("专家修复路径来源", "Expert-repaired paths")], _replay_original_contract_rows(bridge, attempts)) + body += '

' + text( + "跨轨迹控制证据", "Cross-track control evidence") + '' + if bridge.get("moli_failure_review"): + body += ' · ' + text( + "固定轨迹失败复核", "Replay failure review") + '' + return body + '

' def _automatic_failure_counts(attempts: list[dict[str, Any]]) -> Counter[str]: @@ -2357,28 +2328,6 @@ def _automatic_failure_figure(counts: Counter[str]) -> str: return '
' + cards + "
" -def _resource_summary_cards(common_resources: tuple[int, list[list[Any]]]) -> str: - task_count, rows = common_resources - if not task_count: - return '

当前没有四种配置最终都成功的任务,资源差异暂不比较。

' - - def values(engine: str, column: int) -> list[float]: - label = ENGINE_LABELS[engine] - return [float(str(row[column]).split()[0].replace(",", "")) for row in rows if row[1] == label] - - chromium_memory, moli_memory = values("chromium", 5), values("moli", 5) - chromium_tool, moli_tool = values("chromium", 3), values("moli", 3) - chromium_cpu, moli_cpu = values("chromium", 6), values("moli", 6) - return ( - '

Moli 的内存区间更低

' - f'{task_count} 题最终共同成功样本中,Moli 峰值内存 P50 为 {min(moli_memory):.1f}–{max(moli_memory):.1f} MiB;' - f'Chromium 为 {min(chromium_memory):.1f}–{max(chromium_memory):.1f} MiB。

' - '

Chromium 的工具耗时与 CPU 更低

' - f'BrowserTool P50:Chromium {min(chromium_tool):.1f}–{max(chromium_tool):.1f} 秒,Moli {min(moli_tool):.1f}–{max(moli_tool):.1f} 秒;' - f'CPU 时间 P50:Chromium {min(chromium_cpu):.1f}–{max(chromium_cpu):.1f} 秒,Moli {min(moli_cpu):.1f}–{max(moli_cpu):.1f} 秒。

' - ) - - def _article_body( paired: dict[str, Any], attempts: list[dict[str, Any]], @@ -2389,111 +2338,100 @@ def _article_body( automatic_failure_counts: Counter[str], trajectory_replay_bridge: dict[str, Any] | None, ) -> str: - total_task_count = len(tasks) + from .report_i18n import bilingual_text + + text = bilingual_text + task_count = len(tasks) + configurations = {(row['model'], row['engine']) for row in attempts} + configuration_count = len(configurations) + expected_count = task_count * configuration_count + complete = {(row['model'], row['engine'], int(row['task_id'])) for row in attempts} == { + (model, engine, int(task['task_id'])) for model, engine in configurations for task in tasks} + scope = '完整结果' if complete else '已记录结果' excluded = _external_interference_task_ids(verified_causes) - eligible_attempts = [ - row for row in attempts if int(row["task_id"]) not in excluded - ] - diagnostic_task_count = total_task_count - len(excluded) - task_count = total_task_count + eligible = [row for row in attempts if int(row['task_id']) not in excluded] + clean_count = task_count - len(excluded) + failed = [row for row in attempts if float(row.get('score') or 0) < 1] summary = _primary_summary(attempts) - paired = _paired_summary(attempts) - raw_failed_result_count = sum(float(row.get("score") or 0) < 1 for row in attempts) + pairs = _paired_summary(attempts) comparison_rows = [ - [ - MODEL_LABELS[model], - ENGINE_LABELS[item["baseline_engine"]], - ENGINE_LABELS[item["comparison_engine"]], - item["paired"], - item["both_pass"], - item["baseline_only"], - item["comparison_only"], - item["both_fail"], - ] - for model, model_pairs in paired.items() - for item in model_pairs["comparisons"].values() + [MODEL_LABELS[model], ENGINE_LABELS[item['baseline_engine']], + ENGINE_LABELS[item['comparison_engine']], item['paired'], item['both_pass'], + item['baseline_only'], item['comparison_only'], item['both_fail']] + for model, group in pairs.items() for item in group['comparisons'].values() ] - discordant = sum(row[5] + row[6] for row in comparison_rows) - cause_rows = [ - [ - Safe(f'{_e(result_id)}'), - (diagnosis.get("subcategory") or {}).get("zh") or "—", - (diagnosis.get("reason") or {}).get("zh") or "—", - ] - for result_id, diagnosis in sorted(verified_causes.items()) + clean_rows = [ + [MODEL_LABELS[model], ENGINE_LABELS[engine], f"{value['passed']}/{value['completed']}", _pct(value['score'])] + for model, group in _primary_summary(eligible).items() for engine, value in group.items() ] - if raw_failed_result_count: - cause_section = ( - _failure_cause_table(verified_causes) - + '
查看每个失败结果的核验结论' - + _table( - ["结果编号", "原因分类", "核验结论"], - cause_rows, - caption="人工核验用于解释官方失败结果,不修改分数。修复后需要重新运行才能产生新成绩。", - ) - + "
" - if len(cause_rows) == raw_failed_result_count - else f'

当前有 {raw_failed_result_count} 个原始失败结果,详细原因仍在核验中;未核验结果不展示推测。

' + chapters = [] + for model, engine in sorted(configurations): + failures = [row for row in failed if row['model'] == model and row['engine'] == engine] + if not failures: + continue + ids = {_attempt_id(row) for row in failures} + causes = {key: value for key, value in verified_causes.items() if key in ids} + label = f'{MODEL_LABELS[model]} × {ENGINE_LABELS[engine]}' + specimen = next(iter(causes), _attempt_id(failures[0])) + cause = causes.get(specimen) or {} + reason = cause.get('reason') or {} + explanation = text(str(reason.get('zh') or '此结果尚无已核验根因。'), + str(reason.get('en') or 'This result has no verified root cause.')) + chapters.append( + f'

{_e(label)}

' + + text(f'{len(failures)} 个失败结果,其中 {len(causes)} 个已有根因复核。', + f'{len(failures)} failed results; {len(causes)} have a verified cause.') + + _failure_cause_table(causes) + + f'

{_e(specimen)} · {explanation}

' ) - else: - cause_section = '

本轮没有失败结果,无需生成失败根因记录。

' common_count, resource_rows = _common_final_success_resource_rows(attempts) - clean = _primary_summary(eligible_attempts) - clean_rows = [ - [ - MODEL_LABELS[model], - ENGINE_LABELS[engine], - f"{values['passed']}/{values['completed']}", - _pct(values["score"]), - ] - for model, model_summary in clean.items() - for engine, values in model_summary.items() - ] - coverage = review.get("coverage") or {} - reviewed_attempts = int(coverage.get("reviewed_attempts") or 0) - recorded_attempts = int(coverage.get("recorded_attempts") or 0) - clean_task_count = diagnostic_task_count + coverage = review.get('coverage') or {} + reviewed = int(coverage.get('reviewed_attempts') or 0) + recorded = int(coverage.get('recorded_attempts') or 0) return ( - '
Browser Use 能力评测
' - f"

{task_count} 题 Browser Use 完整结果:Chromium 与 Moli 的成功率、代价与失败根因

" - f'

首页按预注册的 {total_task_count} 题展示四种配置的官方 evaluator 结果。其中 {len(excluded)} 题经复核存在验收合同、站点、共享状态或参考状态干扰;这些题仍保留在 90 题官方结果中,另外提供 {diagnostic_task_count} 题事后诊断子集。

' - '

WebArena‑Verified 1.2.3 · CowAgent · 模型推理强度固定为低 · 官方程序化 evaluator

' - '
' - '

90 题核心指标

' - + _table( - ["模型", "浏览器", "通过任务", "通过率", "Agent 总耗时", "BrowserTool 耗时", "Tokens"], - _summary_comparison_rows(summary), - table_id="home-core-metrics", - classes="core-comparison-table", - ) - + f'

每个模型×浏览器配置都保留完整 {task_count} 题。以上是官方 evaluator 决定的最终逻辑结果;物理执行记录可在单题证据页下钻。

' - '

按同题比较浏览器

浏览器对比必须留在同一模型层内。汇总数会隐藏互换的成败任务;下表给出同题对照。

' - + _table(["模型", "浏览器 A", "浏览器 B", "同题数", "两边都通过", "A 通过、B 失败", "A 失败、B 通过", "两边都失败"], comparison_rows, caption=f"每行只比较同一模型下的 {task_count} 个配对任务。") - + f'

两个模型层合计有 {discordant} 个单边通过,可用于定位兼容性问题;样本量和方向都不支持总体优劣判断。查看 {task_count} 题的四配置矩阵 →

' - + _trajectory_replay_control(trajectory_replay_bridge, attempts) - + '

程序判分落点与复核根因

程序判分先说明哪类规则未通过;人工复核再沿最终回答、evaluator 断言、HAR 和 Agent 轨迹追到直接原因。这两个层次需要分开。

程序判分落点

' - + _automatic_failure_figure(automatic_failure_counts) - + '

“网络副作用未通过”只描述程序判分位置。它可能源于 Agent 没有提交,也可能源于 evaluator、站点或 HAR 采集问题。

复核根因

' - + _failure_scope_figure(verified_causes) - + cause_section - + '

Agent 行为

例如漏掉商品、选错最优候选、写错字段,或在 40 步前提前结束。这些属于当前 Agent × 模型配置的真实失败。

验收规则

Task 699 的 evaluator 在比较实际提交前就因单元素 Alternatives 配置抛错;Task 528 要求“不提交”,验收却要求 POST。这些 0 分不能归给模型或浏览器。

站点与环境

Magento 搜索或库存保存服务失效,以及 Task 77 的并发状态污染,都会让正确操作无法通过。

浏览器证据

Moli 的部分 HAR 没有 Referer 或响应正文,例如 Task 595 和 Task 676。页面可能已经操作成功,但 evaluator 无法从网络记录确认;这是浏览器集成层的有效信号。

' - + f'

90 题官方结果与事后诊断分开看

首页主结果始终使用完整 {total_task_count} 题官方 evaluator 分母。为了观察排除已知外部干扰后的差异,下表再按整题、四种配置一致排除 {len(excluded)} 题,得到 {clean_task_count} 题事后诊断子集。

' - + _table(["模型", "浏览器", f"{clean_task_count} 题通过", "通过率"], clean_rows, table_id="home-qualified-results", classes="core-comparison-table") - + f'

这是事后诊断子集,不是正式分数,也不替代 90 题官方结果。{raw_failed_result_count} 个原始失败记录继续保存在结果页和核验记录中。

' - '

审查覆盖与边界

1证据完整性

' - + f'{reviewed_attempts}/{recorded_attempts} 个证据包由程序核对文件存在性、哈希、JSON 可读性、回答 / 分数 / 状态一致性和运行身份。

2官方程序验收

每题按最终回答和必要网络副作用判分;没有 LLM judge。

3失败根因复核

{len(verified_causes)}/{raw_failed_result_count} 个原始最终失败结果逐条阅读错误、断言、最终回答、HAR 和 Agent 轨迹,归类并给出处理边界。

' - + '

审查分为完整性检查和失败根因复核。 前者确认“证据包与 evaluator 记录一致”;后者检查 evaluator 语义,并识别出 Task 699 等问题。已通过结果没有再做独立语义盲审,其判定依据仍是官方 evaluator。查看核验记录 →

' - f'

完整 {task_count} 题的速度与浏览器资源

均值、P50、P95 都在四种完整配置中统一比较。CPU、内存和网络只计浏览器容器,不含 CowAgent 或模型服务;模型会改变动作数量和页面停留时间,因此这里比较整套配置,不能单独归因于浏览器内核。

' - + '

历史轨迹记录的视口为 Chromium 780×437/493、Moli 1920×1080。视口差异会影响渲染、CPU 和内存;以下数值与最低值标记仅描述各自实际运行条件,不是统一视口下的资源效率结论。正式效率比较须统一设置后完整重跑。

' + '
Browser Use

' + + text(f'{task_count} 题 Browser Use {scope}', f'Browser Use results across {task_count} declared tasks') + + '

' + + text(f'声明范围为 {task_count} 个任务、{configuration_count} 种配置,已记录 {len(attempts)}/{expected_count} 个逻辑结果。官方 evaluator 的结果保留不变;另列排除 {len(excluded)} 个已核验外部干扰任务后的 {clean_count} 题诊断子集。', + f'The declared scope contains {task_count} tasks and {configuration_count} configurations; {len(attempts)}/{expected_count} logical results are recorded. Official evaluator results remain unchanged. A separate {clean_count}-task diagnostic subset excludes {len(excluded)} tasks with verified external interference.') + + '

' + + text(f'{task_count} 题核心指标', f'Core metrics across {task_count} tasks') + '

' + + _table(['模型', '浏览器', '通过任务', '通过率', 'Agent 总耗时', 'BrowserTool 耗时', 'Tokens'], + _summary_comparison_rows(summary), table_id='home-core-metrics', classes='core-comparison-table') + + '

' + text('每个模型与浏览器配置使用同一任务范围;结果页保留全部物理执行。', + 'Every configuration uses the same task scope; result pages retain all physical executions.') + '

' + + '

按同题比较浏览器

' + + _table(['模型', '浏览器 A', '浏览器 B', '同题数', '两边都通过', 'A 通过、B 失败', 'A 失败、B 通过', '两边都失败'], comparison_rows) + + _trajectory_replay_control(trajectory_replay_bridge, attempts) + '
' + + '

程序判分落点与复核根因

' + + text('程序判分说明未通过的规则;根因复核解释已有证据支持的原因,两者不互相替代。', + 'Programmatic findings identify failed rules; cause reviews explain the evidence-supported cause. They serve different purposes.') + + _automatic_failure_figure(automatic_failure_counts) + _failure_scope_figure(verified_causes) + + ''.join(chapters) + '

' + text('官方结果与事后诊断', 'Official results and post-hoc diagnostics') + '

' + + _table(['模型', '浏览器', text(f'{clean_count} 题通过', f'Passes across {clean_count} tasks'), '通过率'], clean_rows, + table_id='home-qualified-results', classes='core-comparison-table') + + '

' + text('诊断子集不替代官方分数,原始失败证据继续保留。', + 'The diagnostic subset does not replace official scores. Original failure evidence remains available.') + '

' + + '

审查覆盖与边界

证据完整性

' + + text(f'{reviewed}/{recorded} 个物理执行证据包经过完整性核对。', f'Integrity checks cover {reviewed}/{recorded} physical-execution evidence bundles.') + + '

官方程序验收

' + + text('每题检查最终回答和任务声明的网络副作用,不使用 LLM judge。', 'Each task checks the final answer and declared network effects, without an LLM judge.') + + '

失败根因复核

' + + text(f'{len(verified_causes)}/{len(failed)} 个失败逻辑结果有已绑定的原因复核。', f'Bound cause reviews cover {len(verified_causes)}/{len(failed)} failed logical results.') + + '

' + text('已通过结果没有再做独立语义盲审', 'Passed results have not received an independent blind semantic review.') + '

查看核验记录 →
' + + '

' + text(f'完整 {task_count} 题的速度与浏览器资源', f'Latency and browser resources across {task_count} tasks') + '

' + + text(f'均值、P50、P95 在全部 {configuration_count} 种配置中比较。CPU、内存和网络只计浏览器,不含 Agent 和模型服务。', + f'Mean, P50 and P95 compare all {configuration_count} configurations. CPU, memory and network cover the browser, excluding the Agent and model service.') + _resource_comparison_table(summary) - + '

每项只标记四种配置中数值唯一最低者。CPU 时间与网络总量累计逻辑结果使用的全部执行;内存来自 Browser Use 执行阶段的浏览器容器采样。

' - '

共同成功任务的性能与资源

下表限定为四种配置最终都通过的 ' - + str(common_count) - + ' 题,并以每题最终逻辑结果包含的全部物理执行计算 P50;这样不会把快速失败误当成性能优势。

' - + _table(["模型", "浏览器", "Agent 链路 P50", "BrowserTool P50", "Tokens P50", "峰值内存 P50", "CPU 时间 P50"], resource_rows, table_id="home-resource-table", classes="core-comparison-table") - + _resource_summary_cards((common_count, resource_rows)) - + '

共同成功子集也保留了历史视口差异,不能据此得出统一视口下的资源效率结论。Agent 链路还包含模型推理和页面等待,因此不能解读为纯内核性能。

' - f'

工程结论

完整 {task_count} 题官方结果是本报告的主口径;{clean_task_count} 题事后诊断子集只用于分离已知外部干扰,不改写成绩。

Moli 的内存占用明显更低,但 HAR 完整性、工具耗时和部分任务兼容性还需修复;Chromium 在本轮 Sol 层多通过 3 题,Astra 层通过数相同,但当前每种配置只有一个正式样本,不能外推成总体优势。

下一轮验证

先修复可控的合同、evaluator、站点和共享状态问题,再按同一协议完整重跑 90 题四种配置;历史官方分数在新证据批次出现前保持不变。

' + + text('最低值标记只描述各配置的实际运行条件,不能视为统一视口下的资源效率结论。', + 'Minimum markers describe the recorded configuration; they do not establish resource efficiency at a common viewport.') + + '

共同成功任务的性能与资源

' + + text(f'{common_count} 个任务在全部配置中最终通过;下表以每个逻辑结果的全部物理执行计算 P50。', + f'All configurations finally passed {common_count} tasks. The table computes P50 over all physical executions within each logical result.') + + _table(['模型', '浏览器', 'Agent 链路 P50', 'BrowserTool P50', 'Tokens P50', '峰值内存 P50', 'CPU 时间 P50'], resource_rows, + table_id='home-resource-table', classes='core-comparison-table') + + text('共同成功子集仍保留各配置记录的运行环境,不能据此推断统一条件下的资源效率。', 'The common-success subset retains each configuration’s recorded environment; it does not establish resource efficiency under identical conditions.') + + '
' ) @@ -2702,7 +2640,7 @@ def _attempt_table( cells = "".join(f"{x if isinstance(x, Safe) else _e(x)}" for x in values) rows.append( f'{cells}' ) @@ -2908,17 +2846,20 @@ def save(name: str, title: str, body: str) -> None: _write_json(stage / "data/fix-log.json", {**model, "artifacts": list(artifacts.values())}) -def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_path: Path, include_english: bool = False, language: str | None = None) -> Path: +def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_path: Path, include_english: bool = False, language: str | None = None, profile_path: Path | None = None) -> Path: from harness.report_languages import includes_english, select_report_language + + from .report_i18n import bilingual_text include_english = includes_english(language, include_english) - mac_expansion = json.loads( - (RESEARCH_ROOT / "webarena-mac-expansion.json").read_text(encoding="utf-8") - ) - mac_counts = mac_expansion["official_counts"] - profile = _read_json(Path("evaluation/browser-use/current-report.json")) + if profile_path is None: + raise ValueError("An explicit report profile is required; pass --profile") + if profile_path.is_symlink(): + raise ValueError("Symlinked report profile is forbidden") + profile = _read_json(profile_path) fix_log = _load_fix_log(profile) _configure_engine_labels(profile) _configure_models(profile) + selection_path = report_selection_path(profile, selection_path) selection = load_pilot(selection_path) trajectory_replay_bridge = _load_trajectory_replay_bridge( profile, selection_sha256=selection.sha256 @@ -2961,7 +2902,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat public_attempts = [_public_attempt(r) for r in attempts] attempts_sha256 = {bundle["model"]: bundle["attempts_sha256"] for bundle in bundles} failed_result_ids = {row["id"] for row in public_attempts if float(row.get("score") or 0) < 1} - default_failure_cause_review = load_failure_cause_review() + default_failure_cause_review = load_failure_cause_review(failure_review_path(profile)) verified_causes: dict[str, dict[str, Any]] = {} failure_reviews: dict[str, dict[str, Any]] = {} for bundle in bundles: @@ -2989,11 +2930,17 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat public["verified_failure_subcategory"] = subcategory public["verified_failure_reason"] = reason official = (manifest.get("environment") or {}).get("official_evaluator") or {} + if profile.get("official_dataset"): + frozen = profile["official_dataset"] + if frozen.get("sha256") != official.get("data_checksum"): + raise ValueError("Frozen official dataset does not match the measured task source") tasks = _resolve_tasks( - selection, expected_data_checksum=str(official.get("data_checksum") or "") or None + selection, expected_data_checksum=str(official.get("data_checksum") or "") or None, + dataset_path=(Path(profile["official_dataset"]["path"]) if profile.get("official_dataset") else None), ) task_count = len(tasks) result_count = len(attempts) + reasoning_effort = str(config.get("reasoning_effort") or protocol.get("reasoning_effort") or profile.get("reasoning_effort") or "未记录") max_agent_steps = str( config.get("max_agent_steps") or protocol.get("max_agent_steps") or "未记录" ) @@ -3077,7 +3024,8 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat (stage / "index.html").write_text( _page(title="首页", active="home", body=home), encoding="utf-8" ) - release_data = _read_json(Path("research/report-changes.json")) + _, release_data = bound_json_asset( + profile.get("report_history"), root=profile_path.parent) releases = release_data.get("browser_use_releases") or [] changes = _hero( "Report releases", @@ -3147,6 +3095,9 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat encoding="utf-8", ) + def translated(zh, en): + return Safe(bilingual_text(zh, en)) + benchmarks = [ [ "WebArena‑Verified", @@ -3189,83 +3140,77 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat methodology = _hero( "Methodology", "评最终结果,不评参考点击路径", - "唯一成绩来源是未修改的 WebArena‑Verified 1.2.3 evaluator;确定性证据审计只验证记录一致性。", + translated("唯一成绩来源是运行记录绑定的官方 evaluator;确定性证据审计只验证记录一致性。", "Scores come from the official evaluator bound by the execution records; deterministic auditing checks evidence consistency."), ) + _section( "候选 benchmark 的验收边界", _table(["Benchmark", "评什么", "验收对象", "判分", "问题"], benchmarks), ) + evaluator_counts = Counter( + rule.get("evaluator", "UnknownEvaluator") + for task in tasks + for rule in (task.get("contract") or {}).get("evaluation_contract", []) + ) + selected_sites = Counter(site for task in tasks for site in task.get("sites", [])) + site_images = environment.get("site_images") or config.get("site_images") or {} methodology += _section( "本轮判分链", - f'
  1. 预注册 {task_count} 题
  2. 逐题重置站点
  3. CowAgent 自主规划
  4. 只替换浏览器
  5. 官方 evaluator 验收
  6. 确定性证据复核

官方数据 812 题全部配置 AgentResponseEvaluator,其中 663 题还配置 NetworkEventEvaluator。所有已配置 evaluator 都返回 1.0 才通过。它检查最终结果和必要副作用,不要求复刻参考动作序列。

', + f'
  1. 预注册 {task_count} 题
  2. CowAgent 自主规划
  3. BrowserTool
  4. 官方 evaluator 验收
  5. 确定性证据复核
' + + '

' + translated( + "以下计数来自本轮选中任务的冻结验收合同。所有已配置 evaluator 都返回 1.0 才通过。", + "The counts below come from the selected tasks' frozen acceptance contracts. Every configured evaluator must return 1.0 for a task to pass.") + '

' + + _table(["Evaluator", translated("任务数", "Tasks")], sorted(evaluator_counts.items()), table_id="methodology-evaluators"), ) methodology += _section( f"这 {task_count} 题如何部署并由 Agent 执行", - '
WebArena-Verified 本地部署、CowAgent 自主操作、双浏览器对照与程序化验收流程
图 1 · 从官方站点镜像、逐题隔离与健康检查,到 CowAgent 观察—决策—操作闭环,再到 Chromium/Moli 对照和官方程序化验收。窄屏可横向滚动查看。
' - f"

站点确实部署在本机,不是公网代站。macOS 上的 Docker 由 Colima 虚拟机承载;运行配置记录的 {_e(site_access_host)} 是浏览器容器访问 macOS 宿主发布端口所用的 Colima 网关,不是评测网站自身地址。Shopping、Shopping Admin、Reddit、GitLab 四个官方站点镜像通过 Docker 端口发布到这个入口,独立的 Chromium/Moli 浏览器容器据此访问本地站点。

" - f"

WebArena‑Verified 当前提供 Shopping、Shopping Admin、Reddit、GitLab、Wikipedia、Map 六类环境。本轮 {task_count} 题只覆盖前四类;Wikipedia 与 Map 需要额外大型数据卷,因此没有部署,也没有任务进入成绩。

" - + ( - f"

运行器在每个物理执行前调用 ContainerManager.start(reset=True),只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。本轮 {preparation_receipt_count}/{len(source_attempts)} 个物理执行都保存了环境准备回执,并与对应执行记录绑定。

" - if preparation_receipts_complete - else "

运行器在每个物理执行前调用 ContainerManager.start(reset=True),只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前证据尚未完整保存逐执行环境准备回执,因此不能把每次准备成功当成已独立核验的事实。

" - ) - + f"

预注册文件只含任务 ID、层级和入选理由,不含点击序列。CowAgent 收到自然语言目标后,在最多 {max_agent_steps} 步内循环读取页面观察、请求模型决策并生成下一次 BrowserTool 调用。Chromium 与 Moli 分别连接独立 CDP 浏览器容器;每次物理执行都保留最终 JSON、Agent 事件流、网络 HAR 和 Playwright trace。

" - + _table( - ["站点", "本轮访问地址", "应用端口", "环境控制端口", "状态"], - [ - ["Shopping", f"http://{site_access_host}:7770", "7770", "7771", "本轮已部署"], - [ - "Shopping Admin", - f"http://{site_access_host}:7780/admin", - "7780", - "7781", - "本轮已部署", - ], - ["Reddit", f"http://{site_access_host}:9999", "9999", "9998", "本轮已部署"], - ["GitLab", f"http://{site_access_host}:8023", "8023", "8024", "本轮已部署"], - ["Wikipedia", "—", "8888", "8889", f"未部署;不在 {task_count} 题"], - ["Map", "—", "3030", "3031", f"未部署;不在 {task_count} 题"], - ], - ) - + ( - f'

环境准备证据:{preparation_receipt_count}/{len(source_attempts)} 个物理执行均记录 reset、ready、应用健康检查和登录准备状态;事件流随后证明 CowAgent 自主产生 BrowserTool 调用序列。

' - if preparation_receipts_complete - else '

证据边界:事件流能证明 CowAgent 在准备完成后产生了 BrowserTool 调用序列;镜像 ID、digest、代码提交与网关记录限定了本地运行身份。逐执行环境准备回执尚不完整。

' - ) + '
Browser Use evaluation flow: tasks, recorded environment, autonomous actions, registered browsers and official evaluators
' + + translated("流程图说明职责;本轮部署身份与环境准备范围见下表及逐执行证据。", + "The diagram explains responsibilities. The recorded deployment and preparation coverage are specified below and in each execution's evidence.") + + '

' + + translated( + f"预注册文件不含点击序列。CowAgent 在最多 {max_agent_steps} 步内观察页面、请求模型决策并生成 BrowserTool 调用。具体浏览器、版本和环境以运行记录为准。", + f"The selection contains no click sequence. CowAgent observes pages, requests model decisions and generates BrowserTool calls within a {max_agent_steps}-step budget. Browser versions and environment identity follow the execution records.") + '

' + + _table([translated("环境字段", "Environment field"), translated("本轮身份", "Recorded identity")], [ + [translated("宿主", "Host"), environment.get("platform", "—")], + [translated("Docker 上下文", "Docker context"), config.get("docker_context", "—")], + [translated("站点访问入口", "Site access host"), site_access_host], + ]) + + _table([translated("站点", "Site"), translated("选中任务数", "Selected tasks"), translated("记录的镜像", "Recorded image")], [ + [SITE_LABELS.get(site, site), count, (site_images.get(site) or {}).get("reference", "—")] + for site, count in sorted(selected_sites.items()) + ], table_id="methodology-sites") + + '

' + translated( + f"环境准备回执:{preparation_receipt_count}/{len(source_attempts)} 个物理执行。逐执行的重置、就绪和健康检查状态以对应回执为准。", + f"Environment preparation receipts: {preparation_receipt_count}/{len(source_attempts)} physical executions. Reset, readiness and health-check states follow the corresponding receipts.") + '

' + '', - ) - methodology += _section( - "Mac 上可以继续扩展哪些实例", - f"

下一轮轨迹重放固定使用本报告当前 {task_count} 题,不扩充任务范围。若未来另行扩充 Browser Use 评测,当前四个已实跑站点可预检 {mac_counts['current_four_sites_single_site']} 个单站任务;计入只依赖这四站的跨站任务后,共有 {mac_counts['current_four_sites_including_cross_site']} 个候选,其中 hard 子集 {mac_counts['current_four_sites_hard_including_cross_site']} 个。

" - + _table( - ["环境", "Apple Silicon Mac 状态", "完整集单站题", "hard 单站题", "约束"], - mac_expansion["environments"], - caption="Wikipedia 与 Map 的容量来自官方环境说明;状态中的“实跑”只指本项目已有执行证据。", - ) - + '

最小扩展方案:先从四个已验证站点的 678 个候选中做可完成性预检和分层抽样;Wikipedia、Map 等大数据环境后置。这样扩容不会先引入新的部署变量。

' - '', + f'' + + translated(f"{MODEL_LABELS[model_id]} · {ENGINE_LABELS[engine]} 自主轨迹示例", + f"{MODEL_LABELS[model_id]} · {ENGINE_LABELS[engine]} autonomous trajectory") + '' + for model_id in MODEL_LABELS for engine in ENGINE_LABELS + for example_id in [next((int(task["task_id"]) for task in tasks + if (model_id, engine, int(task["task_id"])) in by_attempt), None)] + if example_id is not None + ) + '核对镜像与运行身份

', ) methodology += _section( "固定协议", _table( ["项目", "规则"], [ - ["Agent / 模型", f"CowAgent;{model};模型推理强度为低"], - ["计划与实际对照", "计划固定任务、数据、模型、提示和资源上限,仅替换浏览器;历史执行的视口实际不一致,不能视为浏览器是唯一变量的实验"], - ["结果口径", "每题×浏览器一个最终逻辑结果;二元通过率;逐题配对"], + ["Agent / 模型", Safe(bilingual_text(f"CowAgent;{model};推理强度 {reasoning_effort}", f"CowAgent; {model}; reasoning effort {reasoning_effort}"))], + ["计划与实际对照", Safe(bilingual_text("模型、提示、工具、资源和执行环境以冻结配置及实际执行记录为准。", "Model, prompt, tool, resource and environment controls follow the frozen configuration and recorded executions."))], + ["结果口径", Safe(bilingual_text("每题×模型×浏览器一个最终逻辑结果;二元通过率;逐题配对。", "One retained logical result per task, model and browser; binary acceptance and task-paired comparison."))], [ "执行上限", - "代码最多允许两次执行;本报告按冻结运行协议选取正式结果,实际执行耗时和模型用量累计", + Safe(bilingual_text( + f"冻结协议的每题执行上限:{protocol.get('max_task_executions') or profile.get('max_task_executions') or '未记录'};实际执行耗时和模型用量累计。", + f"Frozen execution limit per task: {protocol.get('max_task_executions') or profile.get('max_task_executions') or 'not recorded'}; execution latency and model usage are cumulative.")), ], [ "排除", - "不纳入无法稳定验收的实时任务;本轮排除需大型数据卷的 Wikipedia / Map", + Safe(bilingual_text( + "只报告冻结任务清单及其验收合同覆盖的任务。", + "Only tasks and acceptance contracts in the frozen selection are reported.")), ], ["裁判", "官方程序化 evaluator;无 LLM judge;人工不得改分"], ], @@ -3302,16 +3247,18 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat analysis = _hero( "Results", "结果分析", - "四种配置先在同一张表和同一任务矩阵中比较;失败结果可直接打开诊断,查看发生位置、程序报错、复核根因、轨迹、画面和日志。", + "全部参评配置在同一张表和任务矩阵中比较;失败结果可打开诊断和证据。", ) analysis += _section( f"完整 {len(tasks)} 题结果入口", - '

四种配置的通过率、耗时、Tokens 与浏览器资源总表已放在首页,避免在分析页重复。这里直接进入同题矩阵和逐结果失败诊断。

' + '

' + bilingual_text('首页比较全部配置的通过率、耗时、Tokens 和浏览器资源;本页提供同题矩阵与逐结果诊断。', 'The homepage compares pass rates, latency, tokens and browser resources across all configurations. This page owns the same-task matrix and result diagnosis.') + '

' '', ) analysis += _section( f"同题全配置对比:完整 {len(tasks)} 题", - "

每行是一道预注册任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。

" + "

" + bilingual_text( + f"每行是一道预注册任务,{len(MODEL_LABELS) * len(ENGINE_LABELS)} 列对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。", + f"Each row is a registered task, with {len(MODEL_LABELS) * len(ENGINE_LABELS)} columns for model-by-browser configurations. Filter for disagreement, all-pass or all-fail tasks.") + "

" + _same_task_comparison(tasks, attempts), ).replace('
', '
', 1) analysis += _section( @@ -3685,13 +3632,13 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat site_images = environment.get("site_images") or {} reproduce += _section( "部署环境与网络拓扑", - "

本轮 Docker daemon 运行在 macOS 上的 Colima 虚拟机中。站点容器把应用端口发布到 Docker 宿主;两个浏览器容器通过其容器内 host.docker.internal 对应的网关地址访问这些端口。运行时记录的地址是 " - + _e(environment.get("site_access_host") or "未记录") - + "。它属于本轮私有网络,不应复制为其他机器的固定地址。

" + "

" + translated( + "下表展示运行记录中的宿主、容器运行时、站点访问入口与镜像身份。它们属于所选证据,不作为其他报告实例的默认部署配置。", + "The table shows the recorded host, container runtime, site access entry and image identities. These describe the selected evidence and do not prescribe deployment defaults for another instance.") + "

" + _table( ["层", "本轮身份", "作用"], [ - ["宿主", environment.get("platform", "—"), "运行 Colima 与报告 runner"], + ["宿主", environment.get("platform", "—"), translated("记录的执行宿主", "Recorded execution host")], [ "Docker", f"{environment.get('docker_name', '—')} · {environment.get('docker_architecture', '—')} · {environment.get('docker_server_version', '—')}", @@ -3705,7 +3652,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat [ "ARM 兼容模式", (environment.get("site_runtime_compatibility") or {}).get("mode", "未启用"), - "运行部分 amd64 PHP 站点镜像", + translated("记录的运行兼容配置", "Recorded runtime compatibility"), ], ], ) @@ -3750,7 +3697,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat ], ], ) - + '

账号隔离只用于避免用户级对象冲突,不能替代环境 lane。Task 77/771 的旧证据来自修正前的共享状态,正式比较已将整题四种配置一并排除;只有新的完整四配置证据批次才能重新纳入。

', + + '

' + bilingual_text('账号隔离用于避免用户级对象冲突,不能替代环境隔离。已核验的外部干扰与诊断子集见首页。', 'Account isolation prevents user-object collisions; it does not replace environment isolation. The homepage identifies verified external interference and its diagnostic subset.') + '

', ) reproduce += _section( "公开复现入口", @@ -3769,7 +3716,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat ["范围", "本轮做法", "外部付费账号"], [ ["四个自托管站点", "官方 Docker 镜像与内置测试账号;每题前重置", "不需要"], - ["模型 API", f"{model},reasoning=low", "需要相应 API 权限"], + ["模型 API", f"{model}, reasoning={reasoning_effort}", "需要相应 API 权限"], ["Artifact Hub", "只发布脱敏静态报告", "发布者需已有站点权限"], ], ), @@ -3878,6 +3825,8 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat ) audit = { "schema": "lex_browser_use_report/3", + "profile_sha256": _sha256(profile_path), + "template": template_identity("browser-use"), "result_count": len(attempts), "score_authority": { bundle["model"]: ( @@ -3909,7 +3858,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat model_id: review_value.get("bindings") or {} for model_id, review_value in failure_reviews.items() }, - "source_sha256": file_sha256(FAILURE_CAUSE_REVIEW_PATH), + "source_sha256": file_sha256(failure_review_path(profile)), }, "review": { "coverage": review.get("coverage"), @@ -3940,7 +3889,7 @@ def build_report(*, evidence: Path | Iterable[Path], output: Path, selection_pat "models": list(MODEL_LABELS), "model_labels": MODEL_LABELS, "baseline_engine": BASELINE_ENGINE, - "max_task_executions": 2, + "max_task_executions": protocol.get("max_task_executions") or profile.get("max_task_executions"), "environment_reset": ( "official site containers reset before every execution; " f"{preparation_receipt_count}/{len(source_attempts)} execution-scoped " @@ -4072,16 +4021,19 @@ def main() -> None: help="isolated evidence directory for one model; repeat once per model", ) parser.add_argument("--output", required=True, type=Path) + parser.add_argument("--profile", type=Path, required=True) parser.add_argument( "--selection", type=Path, - default=Path("evaluation/browser-use/webarena-verified-pilot.json"), + default=None, ) from harness.report_languages import add_language_argument, primary_report_language add_language_argument(parser) parser.add_argument("--include-english", action="store_true", help="Generate both editions; --language selects the homepage") args = parser.parse_args() - print(build_report(evidence=args.evidence, output=args.output, selection_path=args.selection, include_english=args.include_english, language=args.language or primary_report_language(args.output))) + configuration = _read_json(args.profile) + selection_path = report_selection_path(configuration, args.selection) + print(build_report(evidence=args.evidence, output=args.output, selection_path=selection_path, include_english=args.include_english, language=args.language or primary_report_language(args.output), profile_path=args.profile)) if __name__ == "__main__": diff --git a/harness/browser_use/report_check.py b/harness/browser_use/report_check.py index 9d30091fb..e9768ecb7 100644 --- a/harness/browser_use/report_check.py +++ b/harness/browser_use/report_check.py @@ -6,6 +6,7 @@ import hashlib import json import re +from collections import Counter from pathlib import Path from lexbench_webfetch.paths import contained_path @@ -14,12 +15,16 @@ load_failure_cause_review, validate_failure_cause_review, ) +from harness.browser_use.failure_cause_review import ( + review_path as failure_review_path, +) from harness.browser_use.failure_cause_review import ( sha256 as file_sha256, ) from harness.browser_use.publication import evidence_metadata from harness.browser_use.report import ( PUBLIC_EXECUTION_ARTIFACTS, + SITE_LABELS, _attempt_id, _configure_engine_labels, _configure_models, @@ -28,10 +33,12 @@ _public_attempt, _public_execution, ) -from harness.browser_use.selection import load_pilot +from harness.browser_use.selection import load_pilot, report_selection_path from harness.record_io import load_jsonl from harness.report_languages import check_language_contract, generated_languages +from harness.reporting.assets import bound_json_asset from harness.reporting.presentation import visible_text as _visible_text +from harness.reporting.templates import template_identity from harness.web_fetch.safety import reject_evidence_symlinks FORBIDDEN_READER_TERMS = ( @@ -301,6 +308,41 @@ def _check_public_privacy(report_dir: Path) -> None: raise ValueError(f"published report contains sensitive text: {path}") +def check_methodology_evidence(content: str, tasks: list[dict], environment: dict) -> None: + """Require displayed deployment and evaluator facts to match the selected evidence.""" + from bs4 import BeautifulSoup + + soup = BeautifulSoup(content, "html.parser") + expected = { + "methodology-evaluators": Counter( + rule.get("evaluator", "UnknownEvaluator") + for task in tasks + for rule in (task.get("contract") or {}).get("evaluation_contract", []) + ), + "methodology-sites": Counter( + SITE_LABELS.get(site, site) for task in tasks for site in task.get("sites", []) + ), + } + for table_id, counts in expected.items(): + table = soup.find("table", id=table_id) + if table is None: + raise ValueError("methodology is missing selected-evidence table: " + table_id) + rows = [row.find_all("td") for row in table.select("tbody tr")] + values = { + cells[0].get_text(strip=True): cells[1].get_text(strip=True) + for cells in rows + if len(cells) >= 2 + } + if len(rows) != len(counts) or values != {key: str(value) for key, value in counts.items()}: + raise ValueError("methodology differs from selected evidence: " + table_id) + visible = soup.get_text(" ", strip=True) + for key in ("platform", "site_access_host"): + if environment.get(key) and str(environment[key]) not in visible: + raise ValueError("methodology is missing recorded environment: " + key) + if soup.find(id="deployment-flow") is None: + raise ValueError("methodology is missing the evaluation-flow diagram") + + def check( report_dir: Path, evidence_dir: Path | list[Path], @@ -308,36 +350,45 @@ def check( *, require_complete: bool, authority_root: Path | None = None, + profile_path: Path | None = None, ) -> None: reject_evidence_symlinks(report_dir) for evidence in [evidence_dir] if isinstance(evidence_dir, Path) else evidence_dir: reject_evidence_symlinks(evidence) repository = authority_root if authority_root is not None else Path(__file__).parents[2] - profile_path = contained_path( - repository, "evaluation/browser-use/current-report.json", reject_symlinks=True - ) + if profile_path is None: + if authority_root is None: + raise ValueError( + "An explicit report profile is required; pass --profile or --authority-root" + ) + profile_path = contained_path( + authority_root, "evaluation/browser-use/current-report.json", reject_symlinks=True + ) + if profile_path.is_symlink(): + raise ValueError("Symlinked report profile is forbidden") if not profile_path.is_file(): raise ValueError( "Browser Use release authority is missing; pass --authority-root " - "with the repository's bound evaluation and research files" + "with the selected instance's bound evaluation and research files" ) profile = json.loads(profile_path.read_text(encoding="utf-8")) if profile.get("schema") != "lex_browser_use_current_report/2": raise ValueError("unsupported Browser Use release authority schema") - if selection_path is None: - selection_path = contained_path( - repository, - "evaluation/browser-use/webarena-verified-pilot.json", - reject_symlinks=True, - ) + frozen = profile.get("official_dataset") + if frozen: + dataset_path = Path(frozen["path"]) + if ( + dataset_path.is_symlink() + or not dataset_path.is_file() + or _sha256(dataset_path) != frozen.get("sha256") + or frozen.get("sha256") != profile.get("official_data_checksum") + ): + raise ValueError("Frozen official dataset differs from the instance profile") + selection_path = report_selection_path(profile, selection_path, authority_root=repository) if selection_path.is_symlink(): raise ValueError("Symlinked selection is forbidden") cause_profile = profile.get("failure_cause_review") or {} - cause_path = contained_path( - repository, - cause_profile.get("source_path") or "research/browser-use-failure-cause-review.json", - reject_symlinks=True, - ) + cause_path = failure_review_path(profile, authority_root=repository) if require_complete and cause_profile.get("required_for_publication"): configured_hash = cause_profile.get("source_sha256") if not isinstance(configured_hash, str) or configured_hash != file_sha256(cause_path): @@ -381,6 +432,9 @@ def check( report_dir / "results" / f"{_attempt_id(row)}.html" for row in logical_rows ) chinese_pages = (*pages, *task_pages, *result_pages) + _, history = bound_json_asset(profile.get("report_history"), root=repository) + if json.loads((report_dir / "report-changes.json").read_text()) != history: + raise ValueError("Report history differs from the selected instance") check_language_contract(report_dir) english_pages = tuple( report_dir / "en" / page.relative_to(report_dir) @@ -424,6 +478,10 @@ def check( raise ValueError("report is missing one or more required report pages") selection = load_pilot(selection_path) audit = json.loads(audit_path.read_text(encoding="utf-8")) + if audit.get("profile_sha256") and audit.get("template") != template_identity("browser-use"): + raise ValueError("report bypasses the maintained Browser Use template") + if audit.get("profile_sha256") and audit["profile_sha256"] != _sha256(profile_path): + raise ValueError("report was built from a different instance profile") if audit.get("selection_sha256") != selection.sha256: raise ValueError("report was not built from the current pre-registered selection") rows = measured_rows @@ -527,14 +585,15 @@ def check( or "逐结果覆盖" in review_index ): raise ValueError("review page must present review rounds, not a per-result audit table") - methodology_index = rendered_pages[report_dir / "methodology/index.html"] - for marker in ("192.168.5.2", "Wikipedia", "Map", "环境准备证据"): - if marker not in methodology_index: - raise ValueError(f"methodology is missing deployment evidence boundary: {marker}") + selected_tasks = json.loads((report_dir / "data/selection.json").read_text())["tasks"] + environment = bundles[0]["manifest"].get("environment") or {} + for page in (report_dir / "methodology/index.html", report_dir / "en/methodology/index.html"): + if page in rendered_pages: + check_methodology_evidence(rendered_pages[page], selected_tasks, environment) analysis_index = rendered_pages[report_dir / "analysis/index.html"] for marker in ( "comparison-winner", - "完整 90 题结果入口", + f"完整 {len(selection.tasks)} 题结果入口", "BrowserTool 动作延迟", "action-card-grid", "全部配置 · 单次调用墙钟时间", @@ -549,14 +608,14 @@ def check( guide_index = rendered_pages[report_dir / "index.html"] for marker in ( - "90 题核心指标", + f"{len(selection.tasks)} 题核心指标", "Agent 总耗时", "BrowserTool 耗时", "均值", "P50", "P95", "comparison-winner", - "四种完整配置中统一比较", + f"全部 {len(models) * len(engines)} 种配置中比较", "已通过结果没有再做独立语义盲审", "程序判分落点与复核根因", 'class="verdict-strip"', @@ -564,7 +623,6 @@ def check( 'id="home-qualified-results"', 'class="audit-ladder"', 'id="home-resource-table"', - "results/gpt-6-astra-chromium-699.html#verification", ): if marker not in guide_index: raise ValueError(f"interpretation is missing an evidence-backed reading aid: {marker}") @@ -869,7 +927,8 @@ def check( external_prefixes = ("benchmark_", "site_", "environment_", "mixed_") expected_quarantine = { int(result_id.rsplit("-", 1)[-1]) - for result_id, diagnosis in verified_causes.items() + for cohort in default_cause_review.get("cohorts", {}).values() + for result_id, diagnosis in (cohort.get("results") or {}).items() if str((diagnosis.get("subcategory") or {}).get("code") or "").startswith( external_prefixes ) @@ -907,6 +966,7 @@ def main() -> None: help="Explicit selection; defaults to the bound authority root", ) parser.add_argument("--authority-root", type=Path) + parser.add_argument("--profile", type=Path) parser.add_argument("--require-complete", action="store_true") args = parser.parse_args() check( @@ -915,6 +975,7 @@ def main() -> None: args.selection, require_complete=args.require_complete, authority_root=args.authority_root, + profile_path=args.profile, ) print("Browser Use report integrity check passed") diff --git a/harness/browser_use/report_i18n.py b/harness/browser_use/report_i18n.py index 49a4ef90f..e26876d8f 100644 --- a/harness/browser_use/report_i18n.py +++ b/harness/browser_use/report_i18n.py @@ -27,461 +27,9 @@ ) -TEXT = { - "OpenAI、Anthropic、Google DeepMind、THUDM / ChatGLM、Browser Use。原版 Mind2Web、Online-Mind2Web 与 Mind2Web 2 的任务和评分器不同,不能合并到同一成绩表。": "OpenAI, Anthropic, Google DeepMind, THUDM / ChatGLM and Browser Use. Original Mind2Web, Online-Mind2Web and Mind2Web 2 use different tasks and evaluators; their scores cannot be combined in one table.", - "修复日志": "Fix log", - "配置": "Configuration", - "保留原合同的任务仍有自主执行失败": "Autonomous failures remain under original contracts", - "下表固定排除修订合同任务,按轨迹来源统计自主执行的官方通过数。这是事后诊断,主报告分母仍为 90;历史成功路径经过筛选,不能把此表当成新的能力排名。": "The table excludes the fixed set of revised-contract tasks and counts official autonomous passes by trajectory origin. This is a post-hoc diagnostic; the main report denominator remains 90. Historical paths were selected on success, so this table is not a new capability ranking.", - "全部原合同任务": "All original-contract tasks", - "历史成功路径来源": "Historical-success origins", - "专家修复路径来源": "Expert-repair origins", - "轨迹来源与验收合同": "Trajectory origins and acceptance contracts", - "90 条轨迹中,81 条保留官方原合同,9 条使用修订合同。专家修复轨迹不一定修改验收;历史成功轨迹也可能修订验收。合同差异与路径来源需同时考虑。": "Of 90 trajectories, 81 retain the original official contract and 9 use revised contracts. Expert trajectory repairs do not always change acceptance, and historically successful trajectories may also use revised acceptance. Both contract differences and trajectory origins matter.", - "轨迹来源": "Trajectory origin", - "官方原合同": "Original official contract", - "修订合同": "Revised contract", - "历史成功轨迹": "Historically successful trajectories", - "专家修复轨迹": "Expert-repaired trajectories", - "修订类别": "Revision category", - "验收差异": "Acceptance differences", - "仅修改事件选择": "Event selection only", - "按匹配条件选择保存请求,避免随后请求覆盖。": "Select the matching save request without allowing subsequent requests to replace it.", - "事件选择与预期字段修改": "Event selection and expected fields", - "修订路由、请求字段或 Referer 条件,并匹配所需异步请求。": "Revise routes, request fields, or Referer requirements and match the required asynchronous requests.", - "参考答案或验收要求修改": "Reference answers or acceptance requirements", - "涉及商品名、表单字段、商品选择规则、未提交表单状态及冗余保存要求。": "Changes cover product names, form fields, product selection, unsubmitted form state, and redundant save requirements.", - "轨迹重放把既定 BrowserTool 调用交给浏览器,模型调用数为 0;Chromium 的 90/90 表示这 90 组固定动作、预录回答与绑定验收合同通过了重放验收,其中部分合同经过修订。它不让 Agent 在当次运行中观察、检索、比较、选择或决定何时结束。": "Trajectory Replay sends predetermined BrowserTool calls to the browser with zero model calls. Chromium's 90/90 means these 90 combinations of fixed actions, prerecorded responses, and bound acceptance contracts passed replay validation; some contracts were revised. The Agent does not observe, retrieve, compare, select, or decide when to stop during the run.", - "验收使用轨迹预录回答(recorded_response)与本次网络记录(HAR)。对于检索问答题,通过不证明当前浏览器读到了所需数据,也不证明 Agent 在本次运行中计算出了答案;要求网络事件的合同仍会检查本次 HAR 是否包含对应请求。": "Acceptance uses the trajectory's prerecorded response (recorded_response) and the current run's network record (HAR). For retrieval questions, a pass does not prove that the current browser read the required data or that the Agent computed the answer during this run. Contracts requiring network events still check the current HAR for the corresponding requests.", - "固定动作、预录回答与绑定合同的重放验收结果。": "Replay acceptance for fixed actions, prerecorded responses, and bound contracts.", - "90 题中的 37 道检索题均只使用回答验收,不要求网络事件:36 题沿用原合同,Task 279 使用修订合同。这 37 题的本轮读数与答案尚未验证;原评分保留,其余 53 题也不能替代原 90 题成为新的排名。": "All 37 retrieval tasks in the 90-task set use answer-only acceptance without requiring network events: 36 retain the original contract and Task 279 uses a revised contract. Data reading and answers from the current run remain unverified for these 37 tasks. Original scores are retained; the other 53 tasks cannot replace the original 90 as a new ranking.", - "Task 676 · Sol × Chromium · 验收证据": "Task 676 · Sol × Chromium · Acceptance evidence", - ":官方合同要求带 filters[status]=fraud 的订单列表 XHR。HAR 已记录该请求、200 状态和正确 Referer;": ": The official contract requires the orders-grid XHR with filters[status]=fraud. The HAR records that request, status 200, and the correct Referer; ", - "操作轨迹": "Action trajectory", - "显示 Suspected Fraud 已生效。NetworkEventEvaluator._filter_events_by_criteria 却对 NAVIGATE + GET 只保留最后的文档导航,遗漏 XHR;默认 last_event_only 又可能在同路径请求中选中后来的 notification_area。两个模型的 Chromium 结果都受此影响。": " shows Suspected Fraud active. NetworkEventEvaluator._filter_events_by_criteria keeps only the last document navigation for NAVIGATE + GET, omitting the XHR. The default last_event_only can also select a later notification_area request on the same path. Chromium results for both models are affected.", - "Moli 的同题证据": "Moli evidence for the same task", - "还缺少 XHR Referer。重放修订既改变事件选择,也移除了 Referer 条件;后者并非 Chromium 这次误判的原因。四个配置的原始失败分数保持不变。": " also lacks the XHR Referer. The replay revision changes event selection and removes the Referer requirement; removing that requirement does not explain Chromium's false negative. The original failure scores for all four configurations are unchanged.", - "从失败结果追到决策与运行设置": "Tracing failures to decisions and runtime settings", - "Task 124 · Astra × Chromium": "Task 124 · Astra × Chromium", - ":Agent 在第 9 个决策就结束,未耗尽 40 步。轨迹把 wireless earphone 查询扩大成 earphone,工具结果仍显示 Items 1 to 12 of 24 total,最终却提交 min=0.01、max=287.64。失败发生在候选资格与检索完整性判断;轨迹中有可读结果,不能用浏览器不可操作或步骤不足解释。当前运行器依赖模型执行提示中的完成自检,没有独立验证候选全集。提示要求自检并不等于已经完成自检。": ": The Agent stopped after decision 9, before the 40-step limit. It broadened the query from wireless earphone to earphone; the tool still showed Items 1 to 12 of 24 total, yet the final answer gave min=0.01 and max=287.64. Candidate eligibility and retrieval completeness failed despite readable tool results. The runner relies on the model to perform the prompted completion check and does not independently verify the candidate set. A requested self-check is not evidence that the check occurred.", - "Task 510 · Sol × Moli": "Task 510 · Sol × Moli", - ":第 40 个决策仍在调用工具,最终回答承认尚未结账。harness/browser_use/runner.py 的 _exhausted_agent_steps 检测这一状态,并通过 _max_steps_response 标记未完成。这证明预算上限中断了流程,但所选 Hiquphon Tweeters 本身也不符合官方目标;增加预算能否完成正确购买仍需对照实验。": ": Decision 40 still called a tool, and the final answer acknowledged that checkout was unfinished. In harness/browser_use/runner.py, _exhausted_agent_steps detects this condition and _max_steps_response marks the attempt incomplete. The budget interrupted execution, but the selected Hiquphon Tweeters also differed from the official target. Whether a larger budget enables the correct purchase requires a controlled experiment.", - "两模型均使用 reasoning_effort=low 和 40 步上限。harness/browser_use/model.py 限定 low,runner.py 在创建适配器时也传入 low。现有分数只反映这套 Agent、工具与推理设置;尚无固定观察输入、仅改变推理档位的实验,不能把分数接近解释为模型能力相同。": "Both models used reasoning_effort=low and a 40-step limit. harness/browser_use/model.py requires low, and runner.py also supplies low when creating the adapter. The scores describe this Agent, tool, and reasoning configuration. No experiment held observations fixed while changing only reasoning effort, so similar scores do not establish equal model capability.", - "固定轨迹控制:它验证浏览器执行,不测自主规划": "Fixed-trajectory control: it tests browser execution, not autonomous planning", - "Chromium 的 90/90 不是 CowAgent 的 90/90。": "Chromium's 90/90 is not CowAgent's 90/90.", - "轨迹重放把既定 BrowserTool 调用交给浏览器,模型调用数为 0;它证明 Chromium 能在逐题重置的环境执行这 90 条合格轨迹,并通过同一官方 evaluator。它不让 Agent 在当次运行中观察、检索、比较、选择或决定何时结束。": "Trajectory Replay sends predetermined BrowserTool calls to the browser with zero model calls. It establishes that Chromium can execute these 90 qualified trajectories in per-task reset environments and pass the same official evaluator; it does not let an agent observe, retrieve, compare, select, or decide when to stop in that run.", - "Chromium:90/90": "Chromium: 90/90", - "固定轨迹执行与官方验收的上界控制。": "An upper-bound control for fixed-trajectory execution and official acceptance.", - "Moli:54/90": "Moli: 54/90", - "同一固定路径仍有 36 条失败,不能把浏览器差异归零。": "The same fixed paths still have 36 failures, so browser differences cannot be treated as zero.", - "路径来源:30 + 60": "Trajectory origin: 30 + 60", - "30 条专家修复路径,加 60 条历史成功的 Browser Use 路径。": "30 expert-repair trajectories plus 60 historically successful Browser Use trajectories.", - "后 60 条在进入重放集之前已经按“成功”条件筛选,其中 56 条来自 Sol × Chromium、3 条来自 Sol × Moli、1 条来自 Astra × Chromium。因此固定轨迹成绩受给定路径、预录回答和验收合同共同约束,不能作为 Agent 决策能力、模型能力或完整 90 题自主通过率的反事实估计。": "The latter 60 were selected on prior success before entering the replay set: 56 came from Sol × Chromium, 3 from Sol × Moli, and 1 from Astra × Chromium. The fixed-trajectory result is constrained by the given paths, prerecorded responses, and acceptance contracts; it is not a counterfactual estimate of agent decision-making, model capability, or autonomous pass rate across all 90 tasks.", - "历史复核曾将 Moli 的 36 个固定轨迹失败归为浏览器责任;新发现的视口设置缺陷要求重新核对这一归因。历史 Browser Use 的 Chromium 实际宽度为 780,Moli 为 1920;Task 173 的搜索链接在宽度达到 992 时由网页 CSS 隐藏。该任务的失败不能单凭可见性超时归因于浏览器。历史分数保留,统一视口后的比较须重新冻结完整任务范围与轨迹资格。": "The historical review attributed all 36 Moli fixed-trajectory failures to the browser; the newly identified viewport configuration defect requires that attribution to be reassessed. Historical Browser Use traces show an actual width of 780 for Chromium and 1920 for Moli. Task 173's search link is hidden by site CSS at widths of 992 or more. Its visibility timeout alone cannot establish browser fault. Historical scores remain unchanged; comparison at a common viewport requires a newly frozen full task scope and trajectory qualification.", - "数据绑定:": "Data bindings:", - "跨轨迹控制摘要": "Cross-track control summary", - "36 个 Moli 固定轨迹失败的逐题复核": "Per-task review of 36 Moli fixed-trajectory failures", - "完整执行证据": "Complete execution evidence", - "按原始结构展示本次物理执行的脱敏回答、evaluator、事件流、轨迹、计量与公开画面索引。": "Shows the sanitized response, evaluator record, event stream, trajectory, measurements, and public-frame index for this physical execution in their original structure.", - "执行证据 JSON": "Execution evidence JSON", - "正在读取证据…": "Loading evidence…", - "在页面中查看完整执行证据": "View complete execution evidence in the page", - "打开原始 JSON": "Open raw JSON", - "25 比 25 不是同因平局:五个零分包含四类根因": "25 vs. 25 is not a same-cause tie: five zero scores contain four root-cause classes", - "同一 CowAgent 与同一模型替换浏览器后,30 个配对任务的官方分数没有出现单边胜出,但逐条追查证明相同分数并不代表相同执行结果。根因分别位于官方任务合同、Agent 完成策略、本项目响应协议和证据采集器;Task 506 的 Chromium 与 Moli 甚至不是同一种失败。": "With the same CowAgent and model and only the browser changed, none of the 30 paired tasks produced a one-sided official-score win. Case-by-case investigation shows that equal scores do not imply equal execution outcomes. Root causes lie in the official task contract, Agent completion policy, this project's response protocol, and the evidence collector; Chromium and Moli did not even fail Task 506 in the same way.", - "二、五个零分的根因与修复边界": "2. Root causes and repair boundaries for the five zero scores", - "两个浏览器共享 CowAgent、模型、任务合同与 evaluator,但这不意味着共同零分必然来自共同原因。根因审计把“页面上出现了什么”继续追到产生错误的合同、策略或采集实现,并为每项给出必须通过的复测条件。": "The two browsers share CowAgent, the model, task contracts, and evaluator, but shared zero scores do not necessarily share a cause. The root-cause audit traces visible symptoms to the contract, policy, or collection implementation that produced them and defines a required retest gate for each.", - "根因": "Root cause", - "责任层": "Owning layer", - "证据闭环": "Evidence chain", - "根治措施": "Corrective action", - "复测门槛": "Retest gate", - "表 2|历史官方分数不被人工改写;合同或运行协议修复后必须产生新证据。": "Table 2 | Historical official scores are not manually changed; contract or runtime-protocol fixes require new evidence.", - "可执行结论:": "Actionable conclusion:", - "#68 因官方 expected 与 schema 冲突而退出下一轮干净 A/B;#142 因类别验收边界未定义而退出,直至补齐可观察口径;#168 修正本项目的空结果状态协议;#124 增加候选语义核验和提交前自检;#506 修复 HAR 即时采集、末步工具调用 fail-closed,并在预注册的 40 步上限下复测。旧的 25/30 仍保留为历史官方分数,但不能再解释成五个同因的 Agent 失败。": "Task 68 leaves the next clean A/B because the official expected value conflicts with its schema. Task 142 leaves until its category acceptance boundary is observable. Task 168 requires the project's zero-result status protocol to be fixed. Task 124 requires candidate-semantic verification and a pre-submit check. Task 506 requires immediate HAR body capture, fail-closed handling of a final-turn tool call, and a retest under a preregistered 40-step ceiling. The old 25/30 remains the historical official score, but it can no longer be interpreted as five same-cause Agent failures.", - "根治措施:": "Corrective action:", - "共同零分": "Shared zero scores", - "不等于共同根因": "Do not imply a shared cause", - "根因结论:": "Root-cause conclusion:", - "#68 是官方任务合同冲突,#124 是候选语义核验缺失,#142 是类别验收边界未定义,#168 是本项目响应协议错误;#506 的 Chromium 是 HAR 采集误判,Moli 是执行步数与完成确认问题。": "Task 68 is an official task-contract conflict; Task 124 lacks candidate-semantic verification; Task 142 has no defined category acceptance boundary; Task 168 is a response-protocol defect in this project. For Task 506, Chromium was misjudged because of HAR capture, while Moli ran out of decision steps without confirming completion.", - "任务合同问题": "Task-contract issues", - "2 题": "2 tasks", - "#68 schema 冲突;#142 类别口径缺失": "Task 68 has a schema conflict; Task 142 lacks a category criterion.", - "Agent 协议与策略": "Agent protocol and policy", - "#124 未核验候选;#168 空结果状态错误": "Task 124 did not validate candidates; Task 168 used the wrong zero-result status.", - "不同根因的同分": "Equal score, different causes", - "1 题": "1 task", - "#506:Chromium 采集误判;Moli 未确认完成": "Task 506: Chromium collection false negative; Moli completion was not confirmed.", - "处理原则:": "Treatment:", - "合同不稳定的任务退出下一轮样本;代码与协议缺陷修复后只通过新执行验证,不覆盖历史官方分数。": "Contract-unstable tasks leave the next cohort. Code and protocol fixes are validated only by new executions and never overwrite historical official scores.", - "查看五题根因、证据闭环与复测门槛 →": "View root causes, evidence chains, and retest gates for all five tasks →", - "首页 · Browser Use 评测": "Home · Browser Use evaluation", - "Browser Use 评测": "Browser Use evaluation", - "Chromium 与 Moli:Browser Use 能力实测": "Chromium vs. Moli: a Browser Use capability evaluation", - "评测方法 · Browser Use 评测": "Methodology · Browser Use evaluation", - "这 30 题如何部署并由 Agent 执行": "How the 30 tasks were deployed and run by the agent", - "图 1 · 运行器从官方 WebArena‑Verified 镜像启动本地站点;每个试次重建该题所需站点、等待环境控制器与应用健康、清理浏览器状态并登录测试账号。图是流程说明,真实性证据来自下方运行身份及 70 个试次页的事件、HAR、trace 哈希。": "Figure 1. The runner starts local sites from official WebArena-Verified images. Every attempt rebuilds the sites required by the task, waits for environment-control and application health, clears browser state, and signs in with benchmark accounts. The figure explains the flow; the runtime identities and the events, HAR, and trace hashes on all 70 attempt pages provide the evidence.", - "不是“先人工部署一次,再连接一个现成网页”。": "This was not a one-time manual deployment followed by connections to an existing website.", - "本轮运行器直接调用 WebArena‑Verified 的 ContainerManager.start();每次正式执行和诊断重试前都以 reset=True 启动该题所需的官方站点容器,并停止无关站点。环境控制服务报告 ready 后,还要通过连续应用级健康检查;随后才清 Cookie、登录内置测试账号、打开任务起始页并开始计入 Agent 执行。": "The runner directly calls WebArena-Verified's ContainerManager.start(). Before every primary run and diagnostic retry, it starts the official site containers required by that task with reset=True and stops unrelated sites. After environment control reports ready, the app must pass repeated application-level health checks. Only then does the runner clear cookies, sign in with benchmark accounts, open the task's start page, and begin agent execution.", - "也不是固定动作回放。": "This was not a fixed action replay either.", - "预注册文件只含任务 ID、层级和入选理由,不含点击序列。CowAgent 收到自然语言目标后,在最多 30 步内循环读取页面观察、请求模型决策并生成下一次 BrowserTool 调用。Chromium 与 Moli 的试次分别连接到独立 CDP 浏览器容器;每个试次保存模型最终 JSON、Agent 事件流、网络 HAR 和 Playwright trace。": "The preregistration contains task IDs, tiers, and selection rationales, but no click sequences. Given the natural-language objective, CowAgent repeatedly reads the page observation, requests a model decision, and generates the next BrowserTool call for up to 30 steps. Chromium and Moli attempts connect to separate CDP browser containers. Every attempt retains the final model JSON, agent event stream, network HAR, and Playwright trace.", - "站点来源": "Site source", - "环节": "Stage", - "本轮实际做法": "Implementation in this run", - "可核验证据": "Verifiable evidence", - "四个官方自托管镜像:Shopping、Shopping Admin、Reddit、GitLab": "Four official self-hosted images: Shopping, Shopping Admin, Reddit, and GitLab", - "运行清单记录镜像 ID、digest、架构与 WebArena‑Verified 1.2.3 checksum": "The run manifest records image IDs, digests, architectures, and the WebArena-Verified 1.2.3 checksum", - "逐试次准备": "Per-attempt preparation", - "重建题目所需容器 → env-ctrl ready → 连续健康检查 → 清状态并登录": "Rebuild required containers → env-ctrl ready → repeated health checks → clear state and sign in", - "运行器实现与 manifest 的 environment_reset;站点准备失败不会产生可计分试次": "Runner implementation and manifest environment_reset; failed site preparation cannot produce a scored attempt", - "CowAgent + 模型依据当前页面逐步调用 BrowserTool;无动作脚本": "CowAgent and the model call BrowserTool step by step from the current page; no action script", - "每个试次页公开按时间排序的 BrowserTool 轨迹和脱敏事件下载": "Every attempt page publishes the ordered BrowserTool trajectory and a sanitized event download", - "结果验收": "Outcome evaluation", - "最终 JSON 交给 AgentResponseEvaluator;需要副作用的任务同时检查 HAR": "AgentResponseEvaluator checks the final JSON; tasks requiring side effects also inspect the HAR", - "每个试次页公开规则级 expected / actual;原始 HAR 与 trace 仅公开 SHA-256 和字节数以避免泄露登录态": "Every attempt page publishes rule-level expected/actual values; raw HAR and trace files expose only SHA-256 and byte counts to protect authenticated state", - "证据边界:": "Evidence boundary:", - "事件流能证明 CowAgent 在运行时产生了不同的 BrowserTool 调用序列,站点镜像和运行配置能证明本地环境身份;它们不能公开模型隐藏思维,也不把成功轨迹当成唯一标准路径。": "Event streams establish that CowAgent produced BrowserTool call sequences at runtime, while site images and run configuration establish the local environment identity. They do not expose hidden model reasoning or make a successful trajectory the only valid path.", - "查看 Chromium 自主轨迹示例": "View a Chromium autonomous trajectory", - "查看 Moli 自主轨迹示例": "View a Moli autonomous trajectory", - "核对镜像与运行身份": "Verify image and runtime identities", - "自托管真实交互": "Self-hosted interactive websites", - "最终 JSON + 必要网络副作用": "Final JSON plus required network side effects", - "程序化;无 LLM judge": "Programmatic; no LLM judge", - "部署重,最适合内核 A/B": "Heavy deployment; strongest fit for browser A/B testing", - "离线网页快照": "Offline webpage snapshots", - "每步目标元素与操作": "Target element and action at each step", - "程序化动作指标": "Programmatic action metrics", - "评过程模仿": "Evaluates process imitation", - "136 个真实网站": "136 live websites", - "动作历史与最终完成": "Action history and final completion", - "WebJudge + 人工标签": "WebJudge plus human labels", - "漂移、CAPTCHA、judge 偏差": "Drift, CAPTCHA, and judge bias", - "实时网站视觉交互": "Visual interaction with live websites", - "最终回答与执行证据": "Final answer and execution evidence", - "多模态 LLM + 人工核验": "Multimodal LLM plus human verification", - "精确复现较弱": "Weaker exact reproducibility", - "长链多站搜索": "Long-horizon multi-site search", - "带引用研究答案": "Research answer with citations", - "rubric Agent-as-a-Judge": "Rubric Agent-as-a-Judge", - "非 UI 内核首选": "Not the first choice for UI browser cores", - "100 个混合任务": "100 mixed tasks", - "最终结果与轨迹": "Final outcomes and trajectories", - "Google judge LLM": "Google judge LLM", - "云浏览器条件影响可比性": "Cloud-browser conditions affect comparability", - "200 个长链多站任务": "200 long-horizon multi-site tasks", - "rubric 完成度": "Rubric completion", - "模型评审": "Model grading", - "成本高": "High cost", - "官方数据 812 题全部配置 AgentResponseEvaluator,其中 663 题还配置 NetworkEventEvaluator。所有已配置 evaluator 都返回 1.0 才通过。它检查最终结果和必要副作用,不要求复刻参考动作序列。": "All 812 official tasks configure AgentResponseEvaluator, and 663 also configure NetworkEventEvaluator. A task passes only when every configured evaluator returns 1.0. The evaluators inspect final outcomes and required side effects, not reproduction of a reference action sequence.", - "Agent / 模型": "Agent / model", - "CowAgent;gpt-5.6-sol;reasoning=low": "CowAgent; gpt-5.6-sol; reasoning=low", - "同题、同数据、同模型、同提示和资源上限,只替换浏览器": "Same task, data, model, prompt, and resource limits; only the browser changes", - "每题×浏览器第一次正式执行;二元 pass@1;逐题配对": "First formal run for each task-browser pair; binary pass@1; task-level pairing", - "首轮失败后最多一次,只做恢复诊断;模型传输失败同请求最多重发一次": "At most one retry after primary failure, used only for recovery diagnosis; at most one resend for a model transport failure", - "不纳入无法稳定验收的实时任务;首轮排除需大型数据卷的 Wikipedia / Map": "Exclude live tasks without stable acceptance; the pilot also excludes Wikipedia and Map because of their large data volumes", - "官方程序化 evaluator;无 LLM judge;人工不得改分": "Official programmatic evaluators; no LLM judge; no manual score changes", - "Agent 最终 JSON": "Agent final JSON", - "先按 JSON schema 和数据类型归一化,再比较 task_type、status 与 retrieved_data;按题可声明列表有序或无序。": "Normalize by JSON schema and data type, then compare task_type, status, and retrieved_data; each task may declare ordered or unordered lists.", - "最终回答符合该题公开答案合同。": "The final answer satisfies the task's public answer contract.", - "没有检查回答是通过哪条 UI 路径得到的,也不评操作效率。": "It does not inspect which UI path produced the answer or evaluate action efficiency.", - "本次浏览器 HAR": "HAR from this browser attempt", - "在网络事件中匹配 URL、方法、状态码以及按需声明的 query、POST、响应字段或 Cookie 条件。": "Match URL, method, status code, and, when declared, query, POST, response-field, or cookie conditions in network events.", - "导航或页面变更产生了指定的可观察副作用。": "Navigation or state changes produced the specified observable side effect.", - "没有要求复刻固定点击序列;未声明的页面状态不在验收范围内。": "It does not require a fixed click sequence; undeclared page state is outside the acceptance scope.", - "一题配置的每个 evaluator 都必须得分 1.0,整题才通过。这里没有 LLM-as-a-judge,也没有人工改分。": "Every evaluator configured for a task must score 1.0 for the task to pass. There is no LLM-as-a-judge and no manual score override.", - "本轮没有观察到浏览器成功率差异;这证明对照流程可比,不证明两个内核等价。": "This run found no browser success-rate difference. It establishes a comparable controlled run, not browser equivalence.", - "归因边界:": "Attribution boundary:", - "任务族覆盖": "Task-family coverage", - "样本同时包含信息提取、页面导航和状态变更任务;类别来自每题官方最终答案合同。": "The sample covers retrieval, navigation, and state-changing tasks. Each category comes from the task's official outcome contract.", - "查看这类任务 →": "View this task family →", - "运行与证据是否完整": "Run and evidence completeness", - "正式调用:": "Formal executions:", - "证据完整性审计:": "Evidence integrity audit:", - "可完成性覆盖:": "Measured-solvability coverage:", - "任务数": "Tasks", - "打开逐题横向对照 →": "Open the task-by-task comparison →", - "记录中的验收差异": "Observed acceptance mismatch", - "已观察到的验收差异:": "Observed acceptance mismatch:", - "证据完整性审计": "Evidence integrity audit", - "已审计": "Audited", - "记录不一致": "Record inconsistencies", - "审计结论": "Audit result", - "先看结论": "Headline result", - "当前结论:": "Current conclusion:", - "本轮没有观察到浏览器成功率差异;这证明对照流程可比,不证明两个内核等价。五个共同失败更值得优先检查 Agent 的跨页聚合与最终状态验证。": "This run found no browser success-rate difference. It establishes a comparable controlled run, not browser equivalence. The five shared failures make cross-page aggregation and final-state verification the first agent-level issues to investigate.", - "单边胜出": "One-browser-only wins", - "个完整配对": "complete pairs", - "失败重试恢复": "Failure retry recoveries", - "不回写主分": "Excluded from pass@1", - "调用与核验状态": "Execution and review status", - "调用完整:": "Execution complete:", - "次计划首轮均有结果;失败项的唯一诊断重试也全部完成。": "planned primary runs have results; every single diagnostic retry after a failure is also complete.", - "独立核验:": "Independent review:", - "个试次;未决": "attempts; unresolved", - "。查看核验轮次 →": ". View review history →", - "三类任务概览": "Three task families", - "只在相同任务族与同一批任务内比较;RETRIEVE、NAVIGATE 与 MUTATE 的合同不同。": "Compare browsers within the same task family and task cohort. RETRIEVE, NAVIGATE, and MUTATE use different acceptance contracts.", - "个任务": "tasks", - "两种浏览器": "Both browsers", - "本类首轮": "primary runs in this family", - "浏览器总览": "Browser overview", - "首轮完成": "Primary coverage", - "首轮通过": "Primary passes", - "失败重试": "Failure retries", - "审查结果": "Review result", - "已核验": "reviewed", - "从结论进入证据": "From result to evidence", - "对零分歧、共同失败和下一轮设计的完整论证。": "A complete interpretation of zero disagreements, shared failures, and the next evaluation design.", - "阅读完整解读 →": "Read the interpretation →", - "筛选并下钻两种浏览器试次。": "Filter tasks and inspect both browsers' attempts.", - "打开任务库 →": "Open the task library →", - "首轮、唯一重试和核验状态。": "Primary runs, the single retry, and review status.", - "打开结果库 →": "Open all results →", - "逐题配对比总分更重要": "Paired tasks are more informative than the aggregate score", - "同一任务的两个首轮结果;重试不参与配对主分。": "Both primary results for each task; retries do not enter the paired primary score.", - "配对结果": "Pair outcome", - "两者都通过": "Both pass", - "仅 Chromium": "Chromium only", - "仅 Moli": "Moli only", - "两者都失败": "Both fail", - "本轮未观察到": "Not observed", - "优先排查共同链路": "Inspect the shared stack first", - "精确 McNemar 双侧 p=1.000。零个不一致配对意味着当前样本无法估计内核方向性效应。": "Exact two-sided McNemar p=1.000. With no discordant pairs, this sample cannot estimate a directional browser effect.", - "结论只适用于本轮预注册样本、Agent、模型与运行配置;展示调整不改变测量结果。": "Conclusions apply only to this preregistered sample, agent, model, and runtime configuration. Presentation changes do not alter measured results.", - "候选 benchmark、选择依据与 30 个预注册任务": "Benchmark candidates, selection rationale, and 30 preregistered tasks", - "先区分最终结果、动作过程和模型裁判,再谈采用率;同一家族不同变体的成绩不能合并。": "First separate final-outcome evaluation, action prediction, and model judging. Scores from different variants of one benchmark family are not interchangeable.", - "公开采用信号 Top 3": "Top three public adoption signals", - "采用次数是公开一手材料中的机构数,不是质量分;未找到 DeepSeek、Kimi 公开的一手 UI browser-use benchmark,不等于内部没有评测。": "Counts are institutions found in primary public sources, not quality scores. No primary public UI browser-use benchmark was found for DeepSeek or Kimi; that does not show they have no internal evaluation.", - "Mind2Web 的拆分不是 easy / medium / hard": "Mind2Web splits are not easy, medium, and hard", - "原始 Mind2Web 使用 cross-task、cross-website、cross-domain 衡量泛化;Online‑Mind2Web 没有统一三档;Mind2Web 2 的 short / medium / long 按动作跨度划分。本报告的简单 5、中等 10、困难 15 是预注册的操作长度层级。": "Original Mind2Web uses cross-task, cross-website, and cross-domain splits. Online-Mind2Web has no common three-level difficulty split. Mind2Web 2 uses short, medium, and long action spans. This report's 5/10/15 easy, medium, and hard groups were preregistered by estimated visible operation length.", - "怎样阅读任务 ground truth": "How to read task ground truth", - "参考状态与可完成性证据": "Reference status and solvability evidence", - "“有成功路径证据”不等于存在唯一标准路径;WebArena‑Verified 不要求复刻人工动作序列。": "Evidence of a successful path does not imply one canonical path. WebArena-Verified does not require replaying a human action sequence.", - "30 个任务": "30 tasks", - "全文搜索": "Full-text search", - "首页": "Home", - "账号": "Accounts", - "任务、意图、答案、错误": "Task, intent, answer, or error", - "任务、意图、验收合同": "Task, intent, or acceptance contract", - "层级": "Tier", - "站点": "Site", - "类型": "Type", - "可完成性": "Solvability", - "本轮已有成功轨迹": "Successful trajectory observed", - "仅官方合同": "Official contract only", - "任务": "Task", - "最少步": "Minimum steps", - "入选理由": "Selection rationale", - "官方任务意图": "Official task intent", - "可完成性证据": "Solvability evidence", - "简单": "Easy", - "中等": "Medium", - "困难": "Hard", - "通过": "Pass", - "失败": "Fail", - "首轮": "Primary", - "重试": "Retry", - "失败诊断重试": "Diagnostic retry", - "无": "None", - "官方任务合同、两种浏览器的实际结果与完整自主执行证据。": "Official task contract, both browsers' measured outcomes, and complete autonomous execution evidence.", - "任务身份": "Task identity", - "本项目层级": "Project tier", - "任务类型": "Task type", - "官方 hard subset": "Official hard subset", - "本轮可完成性证据": "Solvability evidence in this run", - "已有成功轨迹": "Successful trajectory observed", - "尚无成功轨迹": "No successful trajectory observed", - "公开任务意图": "Public task intent", - "官方来源:": "Official source:", - "Ground truth · 官方验收合同": "Ground truth · official acceptance contract", - "没有唯一参考点击路径。": "There is no single reference click path.", - "WebArena‑Verified 允许 Agent 自主选择任意 UI 路径;ground truth 是以下最终回答与必要网络副作用的合取。报告因此比较结果合同,而不把某条人工路径当作标准答案。": "WebArena-Verified lets the agent choose any UI path. Ground truth is the conjunction of the final-answer and required network-side-effect contracts below. The report therefore compares outcome contracts rather than treating a human path as the answer key.", - "程序化验收": "Programmatic acceptance", - "该规则必须得分 1.0。": "This rule must score 1.0.", - "匹配选项与结果 schema": "Matching options and result schema", - "参考状态 · 当前环境是否有成功路径证据": "Reference status · evidence of a successful path in the current environment", - "有。": "Yes.", - "本轮至少一个自主试次通过全部官方合同,因此当前站点快照存在已实测的可完成路径。成功轨迹不是唯一标准路径,也不要求另一浏览器复刻。": "At least one autonomous attempt passed every official contract, so this site snapshot has a measured successful path. That trajectory is not the only valid path, and the other browser need not reproduce it.", - "本轮没有。": "Not in this run.", - "官方任务包给出了确定性验收合同,但两种浏览器的首轮和唯一诊断重试都未通过。因此本报告不能独立证明当前快照存在可完成路径,也不能把失败直接归因给浏览器;需要另做不参与计分的人工或 oracle 可完成性核验。": "The official task bundle provides a deterministic acceptance contract, but both browsers failed the primary run and the single diagnostic retry. This report therefore cannot independently prove that the current snapshot is solvable or attribute the failure directly to the browser. A separate non-scoring human or oracle readiness check is required.", - "两种浏览器的全部正式试次": "All formal attempts from both browsers", - "口径": "Attempt class", - "官方结果": "Official verdict", - "端到端耗时": "End-to-end latency", - "证据": "Evidence", - "查看试次": "View attempt", - "共同失败诊断:": "Shared-failure diagnosis:", - "全部试次结果对照": "All-attempt comparison", - "实际最终回答": "Actual final answer", - "失败规则": "Failed rules", - "为什么这样判": "Verdict rationale", - "执行过程": "Execution", - "评最终结果,不评参考点击路径": "Evaluate final outcomes, not a reference click path", - "唯一成绩来源是未修改的 WebArena‑Verified 1.2.3 evaluator;确定性证据审计只验证记录一致性。": "The unmodified WebArena-Verified 1.2.3 evaluator is the only scoring authority. Deterministic evidence review checks record integrity only.", - "候选 benchmark 的验收边界": "Acceptance boundaries of candidate benchmarks", - "评什么": "What it evaluates", - "验收对象": "Acceptance target", - "判分": "Scoring", - "问题": "Limit", - "本轮判分链": "Scoring chain for this run", - "预注册 30 题": "Preregister 30 tasks", - "逐题重置站点": "Reset sites per task", - "CowAgent 自主规划": "CowAgent plans autonomously", - "只替换浏览器": "Change only the browser", - "官方 evaluator 验收": "Run official evaluators", - "确定性证据复核": "Review deterministic evidence", - "固定协议": "Fixed protocol", - "项目": "Item", - "规则": "Rule", - "唯一变量": "Only changed variable", - "计划与实际对照": "Planned and actual controls", - "计划固定任务、数据、模型、提示和资源上限,仅替换浏览器;历史执行的视口实际不一致,不能视为浏览器是唯一变量的实验": "The plan held tasks, data, model, prompt, and resource limits fixed while changing the browser. Actual historical viewports differed, so the browser was not the only experimental variable.", - "历史轨迹记录的视口为 Chromium 780×437/493、Moli 1920×1080。视口差异会影响渲染、CPU 和内存;以下数值与最低值标记仅描述各自实际运行条件,不是统一视口下的资源效率结论。正式效率比较须统一设置后完整重跑。": "Historical traces recorded Chromium viewports of 780×437/493 and Moli viewports of 1920×1080. Viewport differences affect rendering, CPU, and memory. The values and minimum-value markers describe the actual conditions of each run, not resource efficiency at a common viewport. A formal efficiency comparison requires a full rerun with consistent settings.", - "共同成功子集也保留了历史视口差异,不能据此得出统一视口下的资源效率结论。Agent 链路还包含模型推理和页面等待,因此不能解读为纯内核性能。": "The common-success subset also retains the historical viewport differences and cannot establish resource efficiency at a common viewport. Agent-path time includes model inference and page waits, so it does not measure browser-core performance alone.", - "主分": "Primary metric", - "排除": "Exclusions", - "裁判": "Judge", - "两类程序化 evaluator 到底检查什么": "What the two programmatic evaluators check", - "输入": "Input", - "比较方式": "Comparison", - "能证明什么": "What it establishes", - "不能证明什么": "What it does not establish", - "合取规则:": "Conjunction rule:", - "从总体配对下钻到每一次执行": "Drill down from paired outcomes to every execution", - "先定位十个首轮失败的规则与直接原因,再筛选全部 70 次执行并下钻回答、轨迹和日志。": "First locate the exact failed rules for ten primary failures, then filter all 70 executions and inspect answers, trajectories, and logs.", - "汇总": "Summary", - "浏览器": "Browser", - "耗时中位数": "Median latency", - "耗时 P95": "P95 latency", - "Tokens 中位数": "Median tokens", - "耗时包含模型响应、页面等待、站点重置与浏览器执行,不能解释为纯内核速度。": "Latency includes model responses, page waits, site resets, and browser execution. It is not isolated browser-core speed.", - "同题首轮关系对比": "Paired primary-outcome comparison", - "每行固定为同一任务的 Chromium 与 Moli 首轮。关系筛选只改变可见行,不产生新分数。": "Each row fixes Chromium and Moli primary runs for the same task. Relationship filters change visible rows only and do not create new scores.", - "关系": "Relationship", - "共同通过": "Both pass", - "共同失败": "Both fail", - "有分歧": "Disagreement", - "找到": "Showing", - "个配对任务": "paired tasks", - "对照结论": "Pair outcome", - "查看两种浏览器全部试次": "View all attempts for both browsers", - "首轮失败定位": "Primary-failure inventory", - "两种浏览器在同五题失败。下表不是人工猜测:失败环节来自官方 evaluator,补充说明来自 Agent 最终错误和执行记录。": "Both browsers failed the same five tasks. The failure stage comes from the official evaluators; the additional diagnosis comes from the agent's final error and execution record.", - "首轮试次": "Primary attempt", - "直接原因": "Recorded mismatch", - "同题对照": "Same-task comparison", - "任务与两浏览器对照": "Task and both browsers", - "70 个试次": "70 attempts", - "默认只显示失败;可按浏览器、层级、站点、任务类型、首轮/重试、失败环节和全文关键词筛选。": "Failures are shown by default. Filter by browser, tier, site, task type, primary/retry, failure stage, or full-text query.", - "失败环节": "Failure stage", - "最终回答": "Final answer", - "网络副作用": "Network side effect", - "多项": "Multiple stages", - "试次": "Attempt", - "传输重试": "Transport retries", - "从官方 ground truth、实际回答、规则级判定下钻到 CowAgent 的每一步 BrowserTool 调用。": "Follow the official ground truth, actual answer, and rule-level verdict down to every CowAgent BrowserTool call.", - "答案对照": "Answer comparison", - "规则判定": "Rule verdicts", - "执行轨迹": "Trajectory", - "运行与证据": "Runtime and evidence", - "结果与运行": "Outcome and runtime", - "失败定位": "Failure diagnosis", - "自主操作": "Autonomous actions", - "模型 tokens": "Model tokens", - "模型请求": "Model requests", - "传输错误": "Transport errors", - "证据复核": "Evidence review", - "Ground truth 与实际最终回答": "Ground truth and actual final answer", - "官方任务合同": "Official task contract", - "Agent 实际回答": "Agent's actual answer", - "官方 evaluator 逐规则判定": "Official evaluator verdicts by rule", - "所有规则都必须得分 1.0;这里使用 evaluator 已归一化的实际值与期望值直接对照。": "Every rule must score 1.0. This section directly compares the evaluator's normalized actual value with the expected value.", - "官方期望": "Official expectation", - "实际归一化结果": "Normalized actual result", - "断言与错误": "Assertions and errors", - "CowAgent 自主执行轨迹": "CowAgent autonomous trajectory", - "以下顺序来自本次执行的 Agent 事件流,不是固定动作回放。展开可查看调用参数和输出摘录;完整脱敏日志可下载。": "This sequence comes from the run's agent event stream, not a fixed action replay. Expand a step for arguments and an output excerpt; the complete sanitized log is downloadable.", - "调用参数": "Call arguments", - "执行输出节选": "Execution output excerpt", - "运行、日志与证据": "Runtime, logs, and evidence", - "最终回答 JSON": "Final answer JSON", - "Evaluator 明细 JSON": "Evaluator details JSON", - "动作轨迹 JSON": "Trajectory JSON", - "脱敏事件日志 JSON": "Sanitized event log JSON", - "原始证据哈希": "Raw-evidence hashes", - "指标": "Metric", - "值": "Value", - "证据完整性检查": "Evidence completeness checks", - "检查": "Check", - "原始证据文件绑定": "Raw evidence file bindings", - "存在": "Present", - "字节": "Bytes", - "公开策略": "Publication policy", - "返回任务页": "Return to task", - "个试次均完成确定性证据复核": "attempts received deterministic evidence review", - "复核不重新判题,也不是 LLM-as-a-judge;官方 evaluator 仍是唯一成绩来源。": "Review does not rescore tasks and is not LLM-as-a-judge. The official evaluator remains the only scoring authority.", - "核验结论": "Review result", - "记录试次": "Recorded attempts", - "已复核": "Reviewed", - "记分分歧": "Score disagreements", - "未决": "Unresolved", - "复核协议": "Review protocol", - "逐试次覆盖": "Per-attempt coverage", - "结论": "Outcome", - "失败检查数": "Failed check count", - "失败检查项": "Failed checks", - "两条轨道回答不同问题,不能混分": "Two tracks answer different questions and cannot share a score", - "固定 BrowserTool 动作轨道与自主 Agent 轨道都有效,但控制变量和结论边界不同。": "A fixed BrowserTool action track and an autonomous-agent track are both useful, but they control different variables and support different conclusions.", - "仓库中的两个 harness": "The repository's two harnesses", - "轨道": "Track", - "Agent 是否自主": "Agent autonomy", - "回答的问题": "Question answered", - "Browser Use 官方实践": "Browser Use's published evaluation practice", - "公开依据": "Public sources", - "本轮浏览器运行身份、完整任务结果与失败证据入口。": "Runtime identity, complete task results, and failure evidence for this browser.", - "运行身份": "Runtime identity", - "架构": "Architecture", - "镜像 ID": "Image ID", - "首轮成绩": "Primary score", - "首轮耗时中位数": "Median primary latency", - "全部执行记录": "All execution records", - "首轮决定主分;仅失败任务有一次诊断重试,重试不回写主分。": "The primary run determines pass@1. Only failed tasks receive one diagnostic retry, and retries never replace the primary score.", - "复现从公开仓库、预注册题集和固定运行身份开始": "Reproduction starts with the public repository, preregistered cohort, and fixed runtime identities", - "公开包提供脱敏投影;模型回答、Cookie 和完整网络正文不进入 Git 或站点。": "The public bundle provides sanitized projections. Model credentials, cookies, and complete network bodies do not enter Git or the site.", - "浏览器与代码身份": "Browser and code identities", - "公开复现入口": "Public reproduction entry points", - "是代码、题集和锁文件的唯一来源。": "is the canonical source for code, task definitions, and lockfiles.", - "本地站点账号、模型权限与发布权限分开处理": "Local site accounts, model access, and publication access are separate", - "这里只说明权限边界,不展示密钥、Cookie、用户名或个人信息。": "This page describes access boundaries without exposing keys, cookies, usernames, or personal information.", - "权限与安装": "Access and setup", - "范围": "Scope", - "本轮做法": "Configuration in this run", - "取得与安装": "Acquisition and setup", - "外部付费账号": "Paid external account", - "当前发布": "Current release", - "变更": "Change", - "原因": "Reason", - "处理": "Resolution", - "影响": "Impact", - "报告解读": "Interpretation", - "评测集": "Dataset", - "评测方法": "Methodology", - "结果分析": "Results", - "核验记录": "Review", - "方法介绍": "Methods", - "优": "Best", - "更优": "Better", - "单次均值更低": "Lower mean per call", - "通过数更高": "Higher pass count", - "通过率更高": "Higher pass rate", - "同组通过数更高": "Higher pass count in group", - "结果详情导航": "Result detail navigation", - "环境与复现": "Reproduction", - "变更记录": "Changes", - "账号与安装": "Accounts and setup", - "切换主题": "Toggle theme", - "中文": "中文", -} +_TRANSLATIONS = json.loads(Path(__file__).with_name("report-translations.json").read_text(encoding="utf-8")) + +TEXT = dict(_TRANSLATIONS['TEXT_0']) _FAILURE_ANALYSIS = json.loads( (Path(__file__).parents[2] / "research/browser-use-failure-analysis.json").read_text( @@ -513,564 +61,7 @@ # listed: unmatched task text stays verbatim, and Localizer preserves pre/code # blocks containing evaluator JSON and agent output. TEXT.update( - { - "90 题 Browser Use 完整结果:Chromium 与 Moli 的成功率、代价与失败根因": "Complete 90-task Browser Use results: Chromium vs. Moli success, cost, and root causes", - "90 题核心指标": "Core metrics for all 90 tasks", - "每个模型×浏览器配置都保留完整 90 题。以上是官方 evaluator 决定的最终逻辑结果;物理执行记录可在单题证据页下钻。": "Every model-browser configuration retains all 90 tasks. These are the final logical outcomes from the official evaluator; physical execution records remain available on each task's evidence page.", - "首页按预注册的 90 题展示四种配置的官方 evaluator 结果。其中 14 题经复核存在验收合同、站点、共享状态或参考状态干扰;这些题仍保留在 90 题官方结果中,另外提供 76 题事后诊断子集。": "The homepage reports official evaluator outcomes for all 90 preregistered tasks across four configurations. Review found acceptance-contract, site, shared-state, or reference-state interference in 14 tasks. They remain in the official 90-task result, while a separate 76-task post-hoc diagnostic subset is also provided.", - "14 题存在外部干扰": "Fourteen tasks contain external interference", - "官方 90 题结果不改分、不缩减分母;另用 76 题事后诊断子集观察排除已知干扰后的差异。": "The official 90-task result is neither rescored nor given a smaller denominator. A separate 76-task post-hoc subset shows differences after removing known interference.", - "90 题官方结果与事后诊断分开看": "Separate the official 90-task result from post-hoc diagnosis", - "首页主结果始终使用完整 90 题官方 evaluator 分母。为了观察排除已知外部干扰后的差异,下表再按整题、四种配置一致排除 14 题,得到 76 题事后诊断子集。": "The homepage always uses all 90 tasks as the denominator for the official evaluator result. To inspect differences after known external interference, the table below removes the same 14 tasks across all four configurations and shows a 76-task post-hoc diagnostic subset.", - "这是事后诊断子集,不是正式分数,也不替代 90 题官方结果。75 个原始失败记录继续保存在结果页和核验记录中。": "This is a post-hoc diagnostic subset, not an official score and not a replacement for the official 90-task result. All 75 original failure records remain available in the result and review pages.", - "完整 90 题的速度与浏览器资源": "Latency and browser resources across all 90 tasks", - "完整 90 题官方结果是本报告的主口径;76 题事后诊断子集只用于分离已知外部干扰,不改写成绩。": "The complete official 90-task result is the report's primary view. The 76-task post-hoc diagnostic subset only separates known external interference and does not rewrite scores.", - "Moli 的内存占用明显更低,但 HAR 完整性、工具耗时和部分任务兼容性还需修复;Chromium 在本轮 Sol 层多通过 3 题,Astra 层通过数相同,但当前每种配置只有一个正式样本,不能外推成总体优势。": "Moli uses substantially less memory, while HAR completeness, tool latency, and some task compatibility still require fixes. Chromium passes three more tasks in the Sol cohort and ties Moli in the Astra cohort, but one formal sample per configuration cannot establish a general advantage.", - "先修复可控的合同、evaluator、站点和共享状态问题,再按同一协议完整重跑 90 题四种配置;历史官方分数在新证据批次出现前保持不变。": "Fix controllable contract, evaluator, site, and shared-state defects first, then rerun all four configurations across the same 90-task protocol. Historical official scores remain unchanged until a new evidence cohort exists.", - "四种配置总览:完整 90 题官方结果": "Four-configuration overview: complete official results for 90 tasks", - "同题全配置对比:完整 90 题": "Same-task comparison across all configurations: complete 90-task set", - "每行是一道预注册任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is one preregistered task, with one column per model-browser configuration. Filters isolate divergent, all-pass, or all-fail outcomes.", - "这里保留预注册 90 题的全部官方结果;其中 14 题已标记外部干扰。默认显示失败;点击“查看诊断”可核对发生位置、程序报错、复核根因、轨迹、画面和日志。": "This inventory retains official results for all 90 preregistered tasks, with 14 tasks marked for external interference. Failures are shown by default; open a diagnosis to inspect the failure point, program error, reviewed cause, trajectory, frames, and logs.", - # Current home page and dataset-only inventory. - "Chromium 任务通过": "Chromium tasks passed", - "Moli 任务通过": "Moli tasks passed", - "单边通过": "One-browser pass", - "共同失败": "Both fail", - "结果边界:": "Result boundary:", - "失败根因是报告解读的核心证据": "Root causes are core interpretation evidence", - "官方 evaluator 的错误信息只定位未满足的验收规则;根因核验继续检查任务合同、最终回答、网络记录和自主轨迹。未完成核验前,报告保留自动分类,但不展示推测性根因。": "The official evaluator identifies the unmet acceptance rule. Root-cause review then checks the task contract, final answer, network record, and autonomous trajectory. Until that review is complete, the report retains the automatic classification but shows no speculative cause.", - "根因核验:": "Root-cause review:", - "本轮没有失败结果。": "This run has no failed results.", - "解释配对结果、失败根因和结论边界。": "Interpret paired outcomes, failure root causes, and conclusion boundaries.", - "筛选任务并下钻验收合同。": "Filter tasks and inspect their acceptance contracts.", - "一、先看总分,再看同题分歧": "1. Start with aggregate scores, then inspect task-level disagreements", - "二、失败结果必须追到根因": "2. Every failed result requires a root-cause review", - "三、简单、中等、困难都覆盖,但难度只表示操作长度": "3. Easy, medium, and hard are all covered, but difficulty means operation length only", - "四、选择 WebArena‑Verified 是为了可归因的结果验收": "4. WebArena-Verified was selected for attributable outcome evaluation", - "五、结论只适用于本轮受控样本": "5. Conclusions apply only to this controlled sample", - "程序化 evaluator 先给出失败环节;核验再检查最终执行的任务合同、回答、网络记录和 Agent 轨迹,形成更具体的根因子类与原因。共同失败不默认是共同根因,单边失败也不默认是浏览器缺陷。": "The programmatic evaluator first identifies the failed acceptance stage. Review then checks the score-bearing task contract, answer, network record, and agent trajectory to establish a specific root-cause subtype and reason. A shared failure does not imply a shared cause, and a one-browser failure does not automatically imply a browser defect.", - "本轮没有失败结果,无需生成失败根因记录。": "This run has no failed results, so no failure-cause records are needed.", - "先修复已核验根因并形成新证据,再扩大任务与浏览器范围;历史官方分数不被人工覆盖。": "Fix verified root causes and produce new evidence before expanding the task and browser scope. Historical official scores are never overwritten manually.", - "逻辑结果": "Logical result", - "根因子类": "Root-cause subtype", - "已核验根因": "Verified root cause", - "表 2|根因核验不修改官方分数;修复后必须产生新的执行证据。": "Table 2 | Root-cause review does not alter official scores; fixes require new execution evidence.", - "环境准备证据:": "Environment-preparation evidence:", - "未记录": "Not recorded", - "macOS 上的 Docker 由 Colima 虚拟机承载;运行配置记录的": "Docker on macOS runs inside a Colima virtual machine. The run configuration records", - "是浏览器容器访问 macOS 宿主发布端口所用的 Colima 网关,不是评测网站自身地址。Shopping、Shopping Admin、Reddit、GitLab 四个官方站点镜像通过 Docker 端口发布到这个入口,独立的 Chromium/Moli 浏览器容器据此访问本地站点。": "as the Colima gateway through which browser containers reach ports published on the macOS host; it is not the address of an evaluation website. The four official Shopping, Shopping Admin, Reddit, and GitLab images publish Docker ports to this entry point, which the isolated Chromium and Moli containers use to access the local sites.", - "运行器在每个物理执行前调用": "Before every physical execution, the runner calls", - ",只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前证据尚未完整保存逐执行环境准备回执,因此不能把每次准备成功当成已独立核验的事实。": ", retaining only the sites required by that task. After env-ctrl reports ready, it performs repeated application health checks, clears browser state, signs into the benchmark account, and opens the start page. The current evidence does not contain a complete preparation receipt for every execution, so successful preparation cannot be claimed as independently verified for every run.", - "事件流能证明 CowAgent 在准备完成后产生了 BrowserTool 调用序列;镜像 ID、digest、代码提交与网关记录限定了本地运行身份。逐执行环境准备回执尚不完整。": "The event stream shows that CowAgent generated BrowserTool calls after preparation. Image IDs, digests, code commits, and the recorded gateway bind the local runtime identity. Per-execution preparation receipts remain incomplete.", - "逐执行环境准备回执尚不完整;现有记录只能核对运行器配置与镜像身份。": "Per-execution environment-preparation receipts are incomplete; current records can verify only runner configuration and image identity.", - "需要可完成性核验": "Solvability review required", - "已确认失败点": "Confirmed failure points", - "失败结论:": "Failure conclusion:", - "四题的首个可证失败点位于最终答案构造或状态协议,一题在 30 步上限时仍未完成付款提交。现有证据不能把前四题进一步归因到模型、CowAgent 或提示合同中的某一项。": "The first provable failure point is final-answer construction or status encoding in four tasks; one task had not submitted payment when the 30-step limit was reached. The evidence cannot further attribute the first four failures to the model, CowAgent, or prompt contract individually.", - "共享链路诊断": "Shared-stack diagnosis", - "失败归因:": "Failure attribution:", - "5 个共同失败中,四题指向共享 Agent/模型的语义、聚合或输出协议,一题指向共享的步数预算与长链完成;没有发现站点、官方 evaluator 或答案合同错误的证据。": "Among the five shared failures, four point to shared agent/model semantics, aggregation, or output protocol, and one points to the shared step budget and long-horizon completion. No evidence of site, official evaluator, or answer-contract errors was found.", - "这是固定选择的 30 题试验,不是从全部 WebArena‑Verified 任务随机抽样;比例只描述本样本,不外推总体能力。": "This is a fixed 30-task experiment, not a random sample of all WebArena-Verified tasks. Percentages describe this sample only and are not population estimates.", - "共同失败是报告解读的核心证据": "Shared failures are core interpretation evidence", - "25:25 只说明当前样本没有产生浏览器分歧。理解这个结果必须继续看五个共同失败发生在哪个验收点,而不是把平分直接解释成内核等价。": "The 25:25 result means only that this sample produced no browser disagreement. Interpreting it requires locating the acceptance point of each shared failure instead of treating the tie as browser-core equivalence.", - "最终答案数据": "Final-answer data", - "3 题": "3 tasks", - "#68 漏记录;#124 纳入投影仪;#142 多计发箍": "Task 68 omitted a record; Task 124 included a projector; Task 142 added a headband.", - "最终状态协议": "Final-status protocol", - "1 题": "1 task", - "#168 将空结果编码为 SUCCESS": "Task 168 encoded an empty result as SUCCESS.", - "任务完成边界": "Task-completion boundary", - "#506 达到 30 步时尚未提交付款": "Task 506 had not submitted payment at step 30.", - "归因边界:": "Attribution boundary:", - "这些记录确认了失败发生的位置;除 #506 的步数上限外,没有控制变量实验能够继续区分模型判断、CowAgent 策略和提示/响应合同。": "These records establish where acceptance failed. Apart from Task 506's step limit, no controlled experiment in this run can distinguish model judgment, CowAgent policy, and prompt/response contracts.", - "查看五题逐条直接证据与归因边界 →": "View direct evidence and attribution boundaries for all five tasks →", - "逻辑结果:": "Logical results:", - "筛选并下钻两种浏览器结果。": "Filter tasks and inspect results from both browsers.", - "60 个逻辑结果": "60 logical results", - "查看最终判定、耗时、轨迹和核验状态。": "Inspect final verdicts, latency, trajectories, and review status.", - "同一任务的两个浏览器最终逻辑结果。": "Final logical results from both browsers on the same task.", - "零个不一致任务意味着当前固定样本无法估计内核方向性效应;不对全部 Browser Use 任务作统计外推。": "With no discordant tasks, this fixed sample cannot estimate a directional browser effect. It does not support statistical generalization to all Browser Use tasks.", - "评测集· Browser Use 评测": "Dataset · Browser Use evaluation", - "Mind2Web 家族 · 5 家": "Mind2Web family · 5 organizations", - "WebArena 家族 · 3 家": "WebArena family · 3 organizations", - "OpenAI、Anthropic、THUDM / ChatGLM。自托管、可重置、结果验收,更适合浏览器 A/B。": "OpenAI, Anthropic, and THUDM / ChatGLM. Self-hosting, resettable state, and outcome evaluation make it a stronger fit for browser A/B tests.", - "WebVoyager 家族 · 2 家": "WebVoyager family · 2 organizations", - "OpenAI、Google DeepMind。真实网站直观,但网页漂移与 LLM judge 降低精确复现性。": "OpenAI and Google DeepMind. Live websites are intuitive, but page drift and LLM judging reduce exact reproducibility.", - "采用次数来自公开一手材料中的机构去重计数,不是质量分;证据核对日期 2026-09-08。未找到 DeepSeek、Kimi 公开的一手 UI browser-use benchmark,不等于内部没有评测。": "Adoption counts are deduplicated organizations in public primary sources, not quality scores; evidence was checked on 2026-09-08. No primary public UI browser-use benchmark was found for DeepSeek or Kimi, which does not imply that they perform no internal evaluation.", - "WebArena‑Verified 的 ground truth 不是一条固定点击脚本。": "WebArena-Verified ground truth is not a fixed click script.", - "每题声明一个结构化最终回答合同,并在需要确认页面副作用时附加一个或多个网络事件合同。所有合同都通过才记 1 分。任务详情同时展示合同、两种浏览器的actual回答、失败规则和 CowAgent 自主轨迹。": "Each task declares a structured final-answer contract and, when page side effects must be verified, one or more network-event contracts. Every contract must pass for a score of 1. Task details link the contract to both browsers' actual answers, failed rules, and autonomous CowAgent trajectories.", - "题集范围": "Dataset scope", - "本轮在模型执行前固定 30 题,覆盖四个已部署站点以及 RETRIEVE、NAVIGATE、MUTATE 三种最终合同。这里仅描述题目与验收规则;通过率、耗时、轨迹和可完成性观察统一放在结果分析中。": "The 30 tasks were fixed before model execution. They cover four deployed sites and three outcome contracts: RETRIEVE, NAVIGATE, and MUTATE. This page defines tasks and acceptance rules only; pass rates, latency, trajectories, and solvability observations belong in Results.", - "层级 ↕": "Tier ↕", - "站点 ↕": "Site ↕", - "最少步 ↕": "Minimum steps ↕", - "入选理由 ↕": "Selection rationale ↕", - "官方任务意图 ↕": "Official task intent ↕", - "官方 Hard ↕": "Official hard subset ↕", - "任务合同 ↕": "Task contract ↕", - "查看任务合同": "View task contract", - "是": "Yes", - "否": "No", - # Result-detail information architecture. - "先看任务目标与验收结论,再按需展开技术明细和 CowAgent 的每一步 BrowserTool 调用。": "Start with the task objective and acceptance outcome, then expand technical evidence and each CowAgent BrowserTool call as needed.", - "任务与验收": "Task and acceptance", - "查看任务与验收": "View task and acceptance", - "任务要求与验收结果": "Task requirements and acceptance result", - "已确认失败点:": "Confirmed failure point:", - "直接证据:": "Direct evidence:", - "最终答案少了一条必需记录。": "The final answer omitted one required record.", - "两条最终轨迹都已读取《A Christmas Carol》帖子;官方 evaluator 报告 Matched (1/2)。": "Both final trajectories read the A Christmas Carol post; the official evaluator reports Matched (1/2).", - "最终价格聚合纳入了不符合 wireless earphone 语义的商品。": "The final price aggregation included a product that does not satisfy the wireless-earphone semantics.", - "轨迹中的 17,774.32 对应 QFWCJ 4K Short Throw Projector,且与最终错误最大值完全相同。": "In the trajectory, 17,774.32 belongs to a QFWCJ 4K Short Throw Projector and exactly matches the incorrect final maximum.", - "最终求和多计入了 11.99 的 Crystal Headband。": "The final sum incorrectly included the 11.99 Crystal Headband.", - "轨迹列出 50.52 的 conditioner、11.99 的 headband 和 17.99 的 haircolor;80.50=50.52+11.99+17.99,68.51=50.52+17.99。": "The trajectory lists a 50.52 conditioner, an 11.99 headband, and a 17.99 haircolor item; 80.50=50.52+11.99+17.99, while 68.51=50.52+17.99.", - "空结果被编码成错误的最终状态。": "The empty result was encoded with the wrong final status.", - "官方 evaluator 的唯一失败断言是 Expected not_found_error, got success;检索内容同为空。": "The official evaluator's only failed assertion is Expected not_found_error, got success; both sides contain no retrieved item.", - "结账在付款提交之前停止。": "Checkout stopped before payment submission.", - "官方 evaluator 已确认正确商品加入购物车;付款提交与成功页规则失败,最终响应明确记录达到单次运行最大步骤数。": "The official evaluator confirms that the correct product was added to the cart; the payment-submission and success-page rules failed, and the final response explicitly records reaching the run's maximum step count.", - "任务目标": "Task objective", - "Agent 可自主选择 UI 路径;计分只看以下最终回答与必要网络副作用。": "The agent may choose any UI path. Scoring uses only the final answer and required network side effects below.", - "检查 Agent 最终回答的状态、字段和值是否符合官方答案合同。": "Checks whether the status, fields, and values in the agent's final answer satisfy the official answer contract.", - "检查浏览器网络记录中是否出现合同要求的页面副作用。": "Checks whether the browser network record contains the side effect required by the contract.", - "检查该题官方合同声明的结果条件。": "Checks an outcome condition declared by the task's official contract.", - "最终回答符合该规则。": "The final answer satisfies this rule.", - "已观察到满足合同的网络事件。": "A network event satisfying the contract was observed.", - "最终数据与官方期望值不一致。": "The final data differs from the official expectation.", - "浏览器网络记录中没有出现满足该规则的事件。": "No browser network event satisfying this rule was observed.", - "该规则未通过。": "This rule failed.", - "查看官方期望与本次观察": "View the official expectation and this run's observation", - "本次观察": "Observed in this run", - "查看 evaluator 技术明细": "View evaluator technical details", - "所有规则均为官方程序化验收,必须全部得分 1.0。页面先给出可读结论;官方期望、实际观察和断言保留在折叠技术明细及下载文件中。": "All rules are official programmatic checks and every rule must score 1.0. The page leads with a readable conclusion; expectations, observations, and assertions remain in collapsed technical details and downloadable files.", - "证据诊断:": "Evidence-based diagnosis:", - "本诊断不修改官方分数。": "This diagnosis does not change the official score.", - "共享的跨帖聚合与空值处理错误。轨迹已看到两篇符合条件的帖子,说明站点内容存在;失败发生在最终答案整理。": "Shared cross-post aggregation and null-handling error. The trajectory observed both qualifying posts, so the site content was present; the failure occurred while assembling the final answer.", - "共享的跨帖聚合与空值处理错误。轨迹已看到两篇符合条件的帖子,说明站点内容存在;失败发生在最终答案整理。 本诊断不修改官方分数。": "Shared cross-post aggregation and null-handling error. The trajectory observed both qualifying posts, so the site content was present; the failure occurred while assembling the final answer. This diagnosis does not change the official score.", - "共享的搜索结果相关性过滤错误;站内搜索召回过宽是诱因,但 Agent 仍应按任务语义筛选商品。": "Shared search-result relevance filtering error. Broad site-search recall contributed, but the agent still needed to filter products by the task semantics.", - "共享的搜索结果相关性过滤错误;站内搜索召回过宽是诱因,但 Agent 仍应按任务语义筛选商品。 本诊断不修改官方分数。": "Shared search-result relevance filtering error. Broad site-search recall contributed, but the agent still needed to filter products by the task semantics. This diagnosis does not change the official score.", - "共享的商品类别判定错误。订单数据和 evaluator 数值一致,未见站点或计分器异常。": "Shared product-category classification error. Order data and evaluator values agree; no site or scorer anomaly was found.", - "共享的商品类别判定错误。订单数据和 evaluator 数值一致,未见站点或计分器异常。 本诊断不修改官方分数。": "Shared product-category classification error. Order data and evaluator values agree; no site or scorer anomaly was found. This diagnosis does not change the official score.", - "共享的最终状态协议错误,不是检索内容错误,也不是浏览器执行错误。": "Shared final-status protocol error, not a retrieval-content or browser-execution error.", - "共享的最终状态协议错误,不是检索内容错误,也不是浏览器执行错误。 本诊断不修改官方分数。": "Shared final-status protocol error, not a retrieval-content or browser-execution error. This diagnosis does not change the official score.", - "共享的长链任务完成错误:30 步上限前没有完成结账。两种浏览器路径细节不同,但都未越过同一验收终点。": "Shared long-horizon completion error: checkout was not completed within the 30-step limit. Browser paths differed in detail, but neither crossed the same acceptance endpoint.", - "共享的长链任务完成错误:30 步上限前没有完成结账。两种浏览器路径细节不同,但都未越过同一验收终点。 本诊断不修改官方分数。": "Shared long-horizon completion error: checkout was not completed within the 30-step limit. Browser paths differed in detail, but neither crossed the same acceptance endpoint. This diagnosis does not change the official score.", - # Local environment and expansion guidance. - "Mac 上可以继续扩展哪些实例": "Which environments can be expanded on Mac", - "环境": "Environment", - "Apple Silicon Mac 状态": "Apple Silicon Mac status", - "完整集单站题": "Full-set single-site tasks", - "hard 单站题": "Hard single-site tasks", - "约束": "Constraint", - "已在本机实跑": "Measured locally", - "可部署,尚未实跑": "Deployable, not yet measured", - "可尝试,尚未验证": "Feasible to try, not yet verified", - "amd64 镜像;已通过 Colima 转译运行": "amd64 image; already measured through Colima translation", - "arm64 镜像;需约 100 GB 数据;只出现在跨站任务": "arm64 image; about 100 GB of data; appears only in cross-site tasks", - "amd64 镜像;需约 60 GB 数据与 Apple Silicon 转译": "amd64 image; about 60 GB of data plus Apple Silicon translation", - "Wikipedia 与 Map 的容量来自官方环境说明;状态中的“实跑”只指本项目已有执行证据。": "Wikipedia and Map capacity estimates come from the official environment guide. 'Measured' refers only to evidence already collected by this project.", - "最小扩展方案:": "Minimum expansion plan:", - "先从四个已验证站点的 678 个候选中做可完成性预检和分层抽样;Wikipedia、Map 等大数据环境后置。这样扩容不会先引入新的部署变量。": "Preflight solvability and draw a stratified sample from the 678 candidates on the four verified sites first. Defer the large Wikipedia and Map environments so expansion does not begin by adding deployment variables.", - "官方任务集与安装说明": "Official dataset and installation guide", - "官方环境容量说明": "Official environment capacity guide", - "下一轮轨迹重放固定使用本报告当前 90 题,不扩充任务范围。若未来另行扩充 Browser Use 评测,当前四个已实跑站点可预检 655 个单站任务;计入只依赖这四站的跨站任务后,共有 678 个候选,其中 hard 子集 233 个。": "The next trajectory-replay evaluation is fixed to the report's current 90 tasks; its scope will not expand. If Browser Use is expanded separately later, the four locally measured sites support preflight checks for 655 single-site tasks, or 678 candidates including cross-site tasks that use only these four sites, with 233 in the hard subset.", - "站点确实部署在本机,不是公网代站。": "The sites were deployed locally, not replaced by public websites.", - "为什么五个失败任务会完全相同": "Why the same five tasks failed in both browsers", - "二、为什么五个失败任务会完全相同": "2. Why the same five tasks failed in both browsers", - "二、五个共同失败,具体失败在哪里": "2. Where the five shared failures occurred", - "25 比 25 的真正含义:五个共同失败点相同,根因尚未隔离": "What 25 vs. 25 really means: the five failure points match, but the causes are not isolated", - "同一 CowAgent 与同一模型替换浏览器后,30 个配对任务没有一个出现单边胜出。四个共同失败止于最终答案或状态协议,一个止于 30 步上限前的结账流程;现有证据可以定位失败点,但不能把隐藏原因单独归给浏览器、模型或 Agent。": "With the same CowAgent and model and only the browser changed, none of the 30 paired tasks produced a one-browser-only win. Four shared failures ended at final-answer or status-protocol checks, and one ended in checkout before the 30-step limit. The evidence locates the failure points but cannot assign the hidden causes separately to the browser, model, or agent.", - "五、下一轮先扩展四个已验证站点": "5. Expand the four verified sites first", - "官方判定差异": "Official verdict mismatch", - "直接证据": "Direct evidence", - "尚不能判定": "Not established", - "表 2|只陈述最终回答、官方 evaluator 与执行轨迹能够直接证明的事实;不修改官方分数。": "Table 2 | Only facts directly established by the final response, official evaluator, and execution trajectory are stated; scores are unchanged.", - "两个浏览器共享 CowAgent、模型、提示与工具合同、站点状态、30 步上限和 evaluator,只有浏览器实现不同。逐条证据只能确认验收链路中的首个失败点;除 506 题明确触发步数上限外,记录不足以把错误进一步归因到模型、CowAgent 策略或提示合同中的某一项。": "The browsers share CowAgent, model, prompt and tool contract, site state, the 30-step limit, and evaluator; only the browser implementation changes. The evidence establishes the first failed acceptance point. Apart from Task 506 explicitly reaching the step limit, the records cannot attribute the errors further to the model, CowAgent policy, or prompt contract individually.", - "#68、#124、#142 和 #168 的确定事实是最终答案构造或状态协议错误;#506 的确定事实是达到 30 步上限时尚未产生付款提交和成功页事件。站点错误、evaluator 实现错误、答案合同错误以及模型内部决策原因,在这批证据中均未被证明。因而本轮不能单独判定为“模型能力不行”,只能判定当前 CowAgent、模型、提示与工具合同的组合在这些验收点失败。": "For Tasks 68, 124, 142, and 168, the established fact is an error in final-answer construction or status encoding. For Task 506, the established fact is that payment submission and the success-page event were still absent when the 30-step limit was reached. This evidence does not establish a site error, evaluator implementation bug, answer-contract error, or hidden model decision cause. The run therefore cannot isolate the result as a model-capability failure; it establishes only that the current CowAgent, model, prompt, and tool-contract combination failed at these acceptance points.", - "两种浏览器都只返回《The Hobbit》,官方合同还要求《A Christmas Carol》(author=null)。": "Both browsers returned only The Hobbit; the official contract also requires A Christmas Carol with author=null.", - "两种浏览器都返回 min=0.01、max=17774.32;官方合同要求 max=298.00。": "Both browsers returned min=0.01 and max=17774.32; the official contract requires max=298.00.", - "两种浏览器都返回 80.50;官方合同要求 68.51。": "Both browsers returned 80.50; the official contract requires 68.51.", - "两种浏览器都返回 SUCCESS + [];官方合同要求 NOT_FOUND_ERROR + null。": "Both browsers returned SUCCESS + []; the official contract requires NOT_FOUND_ERROR + null.", - "最终逻辑结果均已添加正确商品,但没有付款提交事件和下单成功页事件。": "Both final logical results added the correct product but contain neither a payment-submission event nor an order-success-page event.", - "可确认遗漏发生在页面内容已经进入轨迹之后;现有证据不能判定模型为何丢弃该记录。": "The omission occurred after the page content entered the trajectory; the evidence cannot establish why the model discarded it.", - "可确认错误值来自无关投影仪;现有证据不能区分是模型判断、Agent 策略还是任务提示约束不足导致未过滤。": "The incorrect value is established as coming from an unrelated projector; the evidence cannot distinguish model judgment, agent policy, or insufficient prompt constraints as the cause of the missing filter.", - "可确认差额及其来源;现有证据不能判定模型为何把 headband 纳入任务口径。": "The amount and its source are established; the evidence cannot establish why the model included the headband in scope.", - "可确认是最终响应协议不匹配,不是页面导航失败;现有证据不能判定错误映射来自模型还是 Agent 响应封装。": "This is established as a final-response protocol mismatch, not a navigation failure; the evidence cannot determine whether the incorrect mapping came from the model or agent response wrapper.", - "30 步上限是已确认的直接停止条件;现有证据不能进一步量化模型规划、Agent 调度和浏览器工具开销各自消耗了多少不必要步骤。": "The 30-step limit is the confirmed immediate stop condition; the evidence cannot quantify how many unnecessary steps came from model planning, agent scheduling, or browser-tool overhead.", - "实际差异": "Observed mismatch", - "证据支持的诊断": "Evidence-supported diagnosis", - "置信度": "Confidence", - "高": "High", - "中": "Medium", - "表 2|诊断来自最终回答、官方 evaluator 与执行轨迹的交叉核对;不修改官方分数。": "Table 2 | Diagnoses cross-check final answers, official evaluator output, and execution trajectories; scores are unchanged.", - "两个浏览器共享 CowAgent、模型、提示与工具合同、站点状态、30 步上限和 evaluator,只有浏览器实现不同。5 个共同失败都落在共享层,因此结果高度相关并不反常;它更说明这批题没有触发足以改变最终成败的浏览器差异。": "The browsers share CowAgent, model, prompt and tool contract, site state, the 30-step limit, and evaluator; only the browser implementation changes. All five shared failures occurred in shared layers, so correlated outcomes are expected. More importantly, this cohort did not trigger a browser difference large enough to change the verdict.", - "结论:": "Conclusion:", - "现有证据不支持“网站本身报错”“官方 evaluator 实现错误”或“标准答案错误”。其中 #124 的站内搜索召回过宽会增加歧义,但返回结果仍需要按任务语义过滤。更准确的归因是:四题属于共享 Agent/模型的语义、聚合或输出协议问题,一题属于共享的长链完成与步数预算问题。这不是对模型通用能力的定论,只描述当前 CowAgent、模型与运行协议。": "The evidence does not support a broken site, an official evaluator implementation bug, or an incorrect answer key. Broad site-search recall adds ambiguity to Task #124, but results still need semantic filtering. The best-supported attribution is four shared agent/model semantic, aggregation, or output-protocol errors and one shared long-horizon completion and step-budget error. This is not a general verdict on the model; it applies to the current CowAgent, model, and run protocol.", - "Wikipedia 需要约 100 GB 数据,Map 需要约 60 GB 数据且当前公开镜像为 amd64;二者可作为后续扩展,但没有理由成为本机下一批的前置阻塞。": "Wikipedia requires about 100 GB of data. Map requires about 60 GB and its current public image is amd64. Both can be later expansions, but neither should block the next Mac cohort.", - "保留 CowAgent、模型、reasoning=low 和每题最多两次执行的固定协议,优先加入长页面、复杂编辑器、弹窗、多标签页、上传下载等更可能诱发浏览器差异的任务。实时网站另开 Online‑Mind2Web / WebVoyager 泛化轨道,LLM judge 与人工抽检单列。": "Keep CowAgent, the model, reasoning=low, and at most two executions per task fixed. Prioritize long pages, complex editors, dialogs, multiple tabs, uploads, and downloads that are more likely to expose browser differences. Use a separate Online-Mind2Web / WebVoyager generalization track for live sites, with LLM judging and human sampling reported separately.", - "两种浏览器都返回《The Hobbit》,遗漏《A Christmas Carol》(作者允许为空)。": "Both browsers returned The Hobbit but omitted A Christmas Carol, whose author may be null.", - "两者都把搜索结果中的无关投影仪计入 wireless earphone 价格范围,最大值报为 17,774.32,官方期望为 298.00。": "Both browsers included an unrelated projector in the wireless-earphone price range, reporting 17,774.32 instead of the official 298.00 maximum.", - "两者都汇总为 80.50,官方期望为 68.51;差额来自把水晶发箍计入 hair care/style 商品。": "Both browsers totaled 80.50 instead of 68.51; the difference came from treating a crystal headband as a hair-care/style product.", - "两者都正确找到空集合,却返回 SUCCESS + [];合同要求 NOT_FOUND_ERROR + null。": "Both browsers correctly found an empty set but returned SUCCESS + []; the contract requires NOT_FOUND_ERROR + null.", - "两者都选中正确商品并清空旧购物车,但未产生付款提交与成功页事件。": "Both browsers selected the correct product and cleared the old cart, but neither produced payment-submission and success-page events.", - "macOS 上的 Docker 由 Colima 虚拟机承载;本轮 Colima VM 地址是 192.168.5.3,其默认路由指向 192.168.5.2。后者是 VM 视角下的 macOS 宿主网关,不是虚拟机地址,也不是评测网站。Shopping、Shopping Admin、Reddit、GitLab 四个官方站点镜像通过 Docker 端口发布到这个宿主入口,独立的 Chromium/Moli 浏览器容器据此访问本地站点。": "Docker on macOS runs inside the Colima VM. In this run the VM address is 192.168.5.3 and its default route points to 192.168.5.2. The latter is the macOS host gateway from the VM's perspective, not the VM address or an evaluation website. The four official Shopping, Shopping Admin, Reddit, and GitLab images publish Docker ports through that host entry point, which the independent Chromium and Moli browser containers use to reach the local sites.", - "macOS 上的 Docker 由 Colima 虚拟机承载;本轮 Colima VM 地址是": "Docker on macOS runs inside the Colima VM. In this run the VM address is", - ",其默认路由指向": ", and its default route points to", - "。后者是 VM 视角下的 macOS 宿主网关,不是虚拟机地址,也不是评测网站。Shopping、Shopping Admin、Reddit、GitLab 四个官方站点镜像通过 Docker 端口发布到这个宿主入口,独立的 Chromium/Moli 浏览器容器据此访问本地站点。": ". The latter is the macOS host gateway from the VM's perspective, not the VM address or an evaluation website. The four official Shopping, Shopping Admin, Reddit, and GitLab images publish Docker ports through that host entry point, which the independent Chromium and Moli browser containers use to reach the local sites.", - ",只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前冻结证据保存了代码提交、运行协议和镜像身份;事件流从环境准备完成后开始,因此逐次 reset、ready 与 health 结果未形成独立绑定的准备回执。新版运行协议已要求保存该回执。": ", retaining only the sites required by the task. After env-ctrl reports ready, the runner performs consecutive application health checks, clears browser state, signs in with test accounts, and opens the start page. The frozen evidence retains the code commit, run protocol, and image identities. Because the event stream begins after preparation, per-run reset, readiness, and health outcomes were not bound into a separate preparation receipt. The new run protocol requires that receipt.", - "表 1|30 个任务的逐题最终结果配对。": "Table 1 | Paired final outcomes for 30 tasks.", - # Deployment, timing, browser matrix, and participant framing. - "图 1 · 从官方站点镜像、逐题隔离与健康检查,到 CowAgent 观察—决策—操作闭环,再到 Chromium/Moli 对照和官方程序化验收。窄屏可横向滚动查看。": "Figure 1. From official site images, per-task isolation, and health checks through CowAgent's observe-decide-act loop, then Chromium/Moli comparison and official programmatic evaluation. Scroll horizontally on narrow screens.", - "macOS 上的 Docker 由 Colima 虚拟机承载;Shopping、Shopping Admin、Reddit、GitLab 四个官方镜像把应用端口发布到 Colima/Docker 宿主。Chromium 与 Moli 各自在独立浏览器容器中运行,通过容器内解析到的宿主网关": "On macOS, Docker runs inside a Colima VM. The four official Shopping, Shopping Admin, Reddit, and GitLab images publish their application ports to the Colima/Docker host. Chromium and Moli run in separate browser containers and reach those ports through the host gateway resolved inside each container", - "访问这些端口。这个地址只是本轮私有网络中的宿主入口,不是评测网站或固定产品配置。": "to access the sites. This address is only the host entry point in this run's private network; it is neither a benchmark website nor a fixed product configuration.", - "预注册文件只含任务 ID、层级和入选理由,不含点击序列。CowAgent 收到自然语言目标后,在最多 30 步内循环读取页面观察、请求模型决策并生成下一次 BrowserTool 调用。Chromium 与 Moli 分别连接独立 CDP 浏览器容器;每个逻辑结果保留最终 JSON、Agent 事件流、网络 HAR 和 Playwright trace。": "The preregistration contains task IDs, tiers, and selection rationales, but no click sequence. Given a natural-language objective, CowAgent repeatedly reads the page observation, requests a model decision, and generates the next BrowserTool call for up to 30 steps. Chromium and Moli connect to separate CDP browser containers. Each logical result retains the final JSON, agent event stream, network HAR, and Playwright trace.", - "本轮访问地址": "Address used in this run", - "应用端口": "Application port", - "环境控制端口": "Environment-control port", - "状态": "Status", - "本轮已部署": "Deployed in this run", - "未部署;不在 30 题": "Not deployed; outside the 30 tasks", - "事件流能证明 CowAgent 在准备完成后产生了 BrowserTool 调用序列;镜像 ID、digest、代码提交与网关记录限定了本地运行身份。当前证据没有独立的逐次环境准备 receipt,因此报告不把“每次重置均成功”当成已经独立核验的事实。": "Event streams establish that CowAgent produced BrowserTool calls after preparation. Image IDs, digests, the code commit, and gateway records bound the local runtime identity. The evidence has no independent per-execution preparation receipt, so the report does not treat every reset as independently verified.", - "结果口径": "Result unit", - "每题×浏览器一个最终逻辑结果;二元通过率;逐题配对": "One final logical result per task-browser pair; binary pass rate; task-level pairing", - "执行上限": "Execution limit", - "代码最多允许两次执行;本报告按冻结运行协议选取正式结果,实际执行耗时和模型用量累计": "The code permits at most two executions. The frozen run protocol selects the official result, while actual execution latency and model usage are cumulative.", - "不纳入无法稳定验收的实时任务;本轮排除需大型数据卷的 Wikipedia / Map": "Exclude live tasks without stable acceptance; this run excludes Wikipedia and Map because they require large data volumes", - "结果分析 · Browser Use 评测": "Results · Browser Use evaluation", - "从总体配对下钻到每个逻辑结果": "Drill down from the paired summary to every logical result", - "先看 30 个同题配对,再定位 10 个失败结果的官方规则、actual回答、轨迹和日志。": "Start with 30 same-task pairs, then inspect the official rules, actual answers, trajectories, and logs for the 10 failed results.", - "通过率": "Pass rate", - "Agent 耗时中位数": "Median agent latency", - "Agent 耗时 P95": "P95 agent latency", - "BrowserTool 耗时中位数": "Median BrowserTool latency", - "BrowserTool 耗时 P95": "P95 BrowserTool latency", - "Agent 端到端耗时": "Agent end-to-end latency", - "从浏览器会话准备开始,到 CowAgent 完成、evaluator 所需证据落盘前结束;包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": "Measured from browser-session preparation until CowAgent completes and before evaluator evidence is persisted. It includes sign-in, model inference, orchestration, page waits, BrowserTool, and cleanup, but excludes the preceding Docker site reset.", - "BrowserTool 执行耗时": "BrowserTool execution latency", - "是事件流中每次 browser 工具调用墙钟时间之和,包含工具封装、CDP 往返与页面等待,仍不是纯内核 CPU 时间。": "The sum of wall-clock durations for browser tool calls in the event stream. It includes tool-wrapper, CDP round-trip, and page-wait overhead, so it is still not pure browser-core CPU time.", - "浏览器动作耗时拆分": "Browser action latency breakdown", - "BrowserTool 动作延迟": "BrowserTool action latency", - "每张卡对应一种 CowAgent action。以单次调用墙钟均值作为主要比较值并标记较低者;总耗时与调用数用于解释工作量,不判优。该指标仍包含工具封装、CDP 往返和页面等待,不是纯内核 CPU 时间。": "Each card represents one CowAgent action. Mean wall-clock time per call is the primary comparison and the lower value is marked; total latency and call count describe workload and are not ranked. The metric still includes tool-wrapper, CDP round-trip, and page-wait overhead, so it is not pure browser-core CPU time.", - "单次调用墙钟时间": "Wall time per call", - "按 CowAgent 发出的 BrowserTool action 聚合调用次数与墙钟时间,用于定位 navigate、click、input、snapshot 等动作在哪个浏览器上更慢。该指标比 Agent 总耗时更接近浏览器执行层,但仍包含工具与 CDP 开销。": "Aggregate call counts and wall-clock time by BrowserTool action emitted by CowAgent to locate whether navigate, click, input, snapshot, and other actions are slower in one browser. This metric is closer to the browser execution layer than total agent latency, but still includes tool and CDP overhead.", - "动作": "Action", - "浏览器两两比较": "Pairwise browser comparisons", - "新增浏览器后自动生成每一对浏览器的同题比较;每一行只使用双方都完成的任务,避免把三种以上浏览器压成一个含义不清的总胜负。": "Adding a browser automatically creates a same-task comparison for every browser pair. Each row uses only tasks completed by both browsers, avoiding an ambiguous aggregate win/loss label across three or more browsers.", - "浏览器对": "Browser pair", - "共同任务": "Shared tasks", - "左侧单独通过": "Left-only passes", - "右侧单独通过": "Right-only passes", - "同题浏览器结果矩阵": "Same-task browser result matrix", - "浏览器组合对比": "Browser-set comparison", - "按同一任务比较所选浏览器,不用各浏览器不同的成功样本拼接结论。基准浏览器单选,对比浏览器可多选;新增浏览器只增加选项和结果标签,不改变页面结构。": "Compare selected browsers on the same task instead of combining different success subsets. Choose one baseline browser and one or more comparison browsers; adding a browser only adds an option and outcome label without changing the page structure.", - "同题结果组合": "Same-task outcome combinations", - "选择一个基准浏览器和一个或多个对比浏览器;浏览器标签可直达该题完整结果。": "Choose one baseline browser and one or more comparison browsers. Browser labels link directly to the complete task result.", - "浏览器 A": "Browser A", - "比较条件": "Comparison condition", - "A 通过,其余失败": "A passes, all others fail", - "A 失败,其余通过": "A fails, all others pass", - "所选浏览器全部通过": "All selected browsers pass", - "所选浏览器全部失败": "All selected browsers fail", - "仅所选浏览器通过": "Only the selected browsers pass", - "全部类型": "All task types", - "对比浏览器(可多选)": "Comparison browsers (select multiple)", - "通过浏览器": "Passing browsers", - "失败浏览器": "Failing browsers", - "请至少选择一个对比浏览器": "Select at least one comparison browser", - "每行是一道任务,浏览器作为可扩展列生成;以后增加浏览器时沿用同一张矩阵,不再把页面结构写死为 Chromium 与 Moli。关系筛选只改变可见行,不产生新分数。": "Each row is one task, with browsers generated as extensible columns. Future browsers use the same matrix instead of hard-coding Chromium and Moli into the page structure. Relationship filters change visible rows only and create no new scores.", - "全部通过": "All pass", - "全部失败": "All fail", - "结果有分歧": "Mixed results", - "横向关系": "Cross-browser outcome", - "任务定义": "Task definition", - "任务合同": "Task contract", - "失败结果定位": "Failed-result inventory", - "默认只显示失败;可按浏览器、层级、站点、任务类型、失败环节和全文关键词筛选。": "Failures are shown by default. Filter by browser, tier, site, task type, failure stage, or full-text query.", - "动作数": "Actions", - "查看逐规则判定": "View rule-level verdicts", - "累计自主操作": "Cumulative autonomous actions", - "Agent 链路耗时包含会话准备、登录、模型推理、调度、工具执行和收尾,不包含 Docker 站点重置;BrowserTool 耗时是全部 browser 工具调用的墙钟时间之和,不等同于纯内核时间。": "Agent-stack latency includes session preparation, sign-in, model inference, orchestration, tool execution, and cleanup, but excludes Docker site reset. BrowserTool latency is the sum of wall-clock time for all browser tool calls and is not pure browser-core time.", - "执行未完成;错误信息见运行与证据。": "Execution did not complete; see runtime and evidence for the error.", - # Result pages and audit boundaries. - "Agent 端到端耗时包含会话准备、登录、模型推理、调度、工具执行和收尾,不包含 Docker 站点重置;BrowserTool 执行耗时是全部 browser 工具调用的墙钟时间之和,不等同于纯内核时间。": "Agent end-to-end latency includes session preparation, sign-in, model inference, orchestration, tool execution, and cleanup, but excludes Docker site reset. BrowserTool execution latency is the sum of wall-clock time for all browser tool calls and is not pure browser-core time.", - "公开包包含脱敏的回答、evaluator、事件流与轨迹。原始 HAR 和 Playwright trace 可能携带登录态、Cookie、页面快照或请求正文,因此不公开;其字节数和 SHA-256 保留用于证明证据绑定。": "The public bundle contains sanitized answers, evaluator records, event streams, and trajectories. Raw HAR and Playwright traces may contain authenticated state, cookies, page snapshots, or request bodies, so they are withheld; byte counts and SHA-256 hashes retain the evidence binding.", - "整次 Agent 运行的容器计量只作诊断;不同进程模型下不直接比较内存或 PID。": "Whole-run container measurements are diagnostic only; memory and PID are not compared directly across different process models.", - "全部官方 evaluator 均通过。": "All official evaluators passed.", - "核验记录 · Browser Use 评测": "Review · Browser Use evaluation", - "证据完整性与独立结果复核是两个指标;本轮只完成前者,官方 evaluator 仍是唯一成绩来源。": "Evidence integrity and independent result review are separate metrics. This run completed only the former; the official evaluator remains the sole scoring authority.", - "证据完整性": "Evidence integrity", - "文件、哈希与分数一致性": "File, hash, and score consistency", - "独立结果复核": "Independent result review", - "尚未进行语义盲审": "No semantic blind review performed", - "完整性未决": "Integrity issues unresolved", - "完整性检查确认回答、HAR、Agent trace、事件和 evaluator 明细存在,并核对哈希、分数、状态与官方版本。": "Integrity checks confirm that answers, HAR, agent traces, events, and evaluator details exist, and verify hashes, scores, statuses, and the official version.", - "复核边界:": "Review boundary:", - "本轮没有第二名人工或模型对结果做盲审,因此“0 个完整性未决”不能解读为结果已经独立语义复核。": "No second human or model blind-reviewed the results, so '0 unresolved integrity issues' must not be interpreted as independent semantic review.", - "审计项": "Audit item", - "回答、事件、HAR、trace 与 evaluator 文件完整且哈希绑定": "Answers, events, HAR, traces, and evaluator files are complete and hash-bound", - "成绩来源": "Scoring authority", - "只接受未修改的 WebArena‑Verified evaluator 输出": "Accept only unmodified WebArena-Verified evaluator output", - "一致性": "Consistency", - "公开状态、分数、官方版本与原始记录一致": "Published status, score, and official version match the source record", - "争议处理": "Dispute handling", - "任何不一致或缺失证据均阻止发布;人工不得改分": "Any inconsistency or missing evidence blocks release; humans may not change scores", - "逐结果覆盖": "Per-result coverage", - # Participants, reproduction, and account boundaries. - "方法介绍 · Browser Use 评测": "Participants · Browser Use evaluation", - "本轮 A/B 的唯一变化项是浏览器实现;CowAgent、模型、任务、资源上限、站点与 evaluator 都是共同实验条件。": "The browser implementation is the only changed variable in this A/B test. CowAgent, the model, tasks, resource limits, sites, and evaluators are shared experimental conditions.", - "两个参评浏览器": "Two browser participants", - "参评浏览器": "Browser participant", - "运行版本": "Runtime version", - "接入方式": "Integration", - "本轮成绩": "Result in this run", - "独立容器 · CDP · CowAgent BrowserTool": "Dedicated container · CDP · CowAgent BrowserTool", - "更完整地说,一个可复现的“参评方法”是整套 Agent 栈;但本轮所有栈字段都保持一致,只有浏览器后端不同,所以报告按浏览器命名选手。不能把 CowAgent、gpt‑5.6‑sol 或 WebArena‑Verified 也算成独立选手。": "Strictly, a reproducible participant is the whole agent stack. In this run every stack field is held constant except the browser backend, so participants are named by browser. CowAgent, gpt-5.6-sol, and WebArena-Verified are not additional participants.", - "共同实验条件": "Shared experimental conditions", - "栈字段": "Stack field", - "本轮固定值": "Fixed value in this run", - "角色": "Role", - "自主观察、规划并调用 BrowserTool": "Observe, plan, and call BrowserTool autonomously", - "模型": "Model", - "为两个选手生成决策": "Generate decisions for both participants", - "任务与站点": "Tasks and sites", - "同一 30 题;同一批本地站点镜像": "Same 30 tasks; same local site images", - "提供一致输入与网页状态": "Provide identical inputs and website state", - "动作接口": "Action interface", - "把 Agent 动作交给浏览器": "Deliver agent actions to the browser", - "验收": "Evaluation", - "WebArena‑Verified 1.2.3 官方 evaluator": "Official WebArena-Verified 1.2.3 evaluator", - "对最终 JSON 与必要网络副作用判分": "Score final JSON and required network side effects", - "资源与上限": "Resources and limits", - "相同 CPU、内存、viewport 与最多 30 步": "Same CPU, memory, viewport, and 30-step limit", - "控制运行预算": "Control the execution budget", - "这 30 题由 CowAgent 自主执行。": "CowAgent executes all 30 tasks autonomously.", - "仓库中的 Browser Trajectory Replay(轨迹重放)固定动作微基准属于另一条实验轨道,不是本报告的参评选手,也不计入这 30 题成绩。": "The repository's fixed-action Browser Trajectory Replay microbenchmark is a separate experimental track, not a participant in this report, and is excluded from these 30 task results.", - "Browser Use 公开使用 BU Bench V1、Odysseys、Online‑Mind2Web 和 Stealth Bench。可借鉴的是运行前确认任务可完成、保存逐题 trace、断点续跑,并把站点不可完成与 Agent 真失败分开。Stealth Bench 测反机器人检测,不能替代自主任务成功率。": "Browser Use publicly uses BU Bench V1, Odysseys, Online-Mind2Web, and Stealth Bench. Useful practices include confirming task solvability before execution, retaining per-task traces, resuming interrupted runs, and separating unsolvable sites from genuine agent failures. Stealth Bench measures bot detection and cannot replace autonomous task success rate.", - "环境与复现 · Browser Use 评测": "Reproduction · Browser Use evaluation", - "协议": "Protocol", - "运行配置选择集 SHA-256": "Run-config selection SHA-256", - "当前选择集 SHA-256": "Current selection SHA-256", - "部署环境与网络拓扑": "Deployment environment and network topology", - "本轮 Docker daemon 运行在 macOS 上的 Colima 虚拟机中。站点容器把应用端口发布到 Docker 宿主;两个浏览器容器通过其容器内": "The Docker daemon for this run operates in a Colima VM on macOS. Site containers publish application ports to the Docker host; both browser containers access them through the", - "对应的网关地址访问这些端口。运行时记录的地址是": "gateway corresponding to host.docker.internal inside each container. The address recorded at runtime is", - "。它属于本轮私有网络,不应复制为其他机器的固定地址。": ". It belongs to this run's private network and must not be copied as a fixed address for another machine.", - "层": "Layer", - "本轮身份": "Identity in this run", - "作用": "Purpose", - "宿主": "Host", - "运行 Colima 与报告 runner": "Runs Colima and the report runner", - "承载站点和浏览器容器": "Hosts site and browser containers", - "浏览器到宿主网关": "Browser-to-host gateway", - "访问站点容器发布端口": "Accesses published ports from site containers", - "ARM 兼容模式": "ARM compatibility mode", - "未启用": "Disabled", - "运行部分 amd64 PHP 站点镜像": "Runs selected amd64 PHP site images", - "站点镜像": "Site image", - "镜像引用": "Image reference", - "环境隔离与并行边界": "Environment isolation and parallelism boundary", - "修正后的运行器把一个 Docker daemon 及其固定站点端口视为一个环境 lane。同一 lane 内的任务和浏览器必须串行:每次尝试先重置站点,再完成 Agent、证据收尾和 evaluator,整个周期由跨进程锁保护。缺少 Docker daemon 身份时直接停止,不降级为无锁执行。": "The corrected runner treats one Docker daemon and its fixed site ports as one environment lane. Tasks and browsers in the same lane run serially: each attempt resets the sites first, then completes the Agent run, evidence finalization, and evaluation while one cross-process lock protects the full lifecycle. If the Docker daemon identity is unavailable, execution stops instead of falling back to an unlocked run.", - "运行方式": "Execution mode", - "是否允许": "Allowed", - "隔离要求": "Isolation requirement", - "一套站点环境,Chromium 与 Moli 串行": "One site stack; Chromium and Moli run serially", - "允许,默认方案": "Allowed; default", - "同一 daemon 锁;每次尝试前重置并核对初态": "One daemon-scoped lock; reset and verify initial state before every attempt", - "一套站点环境,两个浏览器并行": "One site stack; two browsers run in parallel", - "不允许": "Not allowed", - "账号不同仍会共享对象、索引、缓存和后台任务": "Separate accounts still share objects, indexes, caches, and background jobs", - "多个任务并行": "Multiple tasks in parallel", - "允许": "Allowed", - "每个 worker 使用独立 daemon、站点容器、网络、端口和可写卷;镜像只读层可复用": "Each worker uses an independent daemon, site containers, network, ports, and writable volumes; read-only image layers may be shared", - "账号隔离只用于避免用户级对象冲突,不能替代环境 lane。Task 77/771 的旧证据来自修正前的共享状态,正式比较已将整题四种配置一并排除;只有新的完整四配置证据批次才能重新纳入。": "Account isolation only prevents user-scoped object conflicts; it does not replace an environment lane. The old evidence for Tasks 77 and 771 came from shared state before this correction, so all four configurations for each task are excluded from the formal comparison. Readmission requires a new complete four-configuration evidence cohort.", - "逐执行的环境准备 receipt 尚未包含在这批冻结证据中;现有记录可核对运行器配置与镜像身份,但不能独立重放每次 reset/ready/health 的完整证明链。": "Per-execution environment-preparation receipts are absent from this frozen evidence. Existing records verify runner configuration and image identities but cannot independently replay the complete reset/ready/health proof chain for every execution.", - "每个逻辑结果的脱敏回答、evaluator、事件和轨迹位于": "Sanitized answers, evaluator records, events, and trajectories for each logical result are under", - "给出全部 60 个逻辑结果的页面与下载路径。": "lists page and download paths for all 60 logical results.", - "账号 · Browser Use 评测": "Accounts · Browser Use evaluation", - "不展示密钥、Cookie 或真实个人信息;官方合同中的内置测试邮箱和用户名作为合成 ground truth 保留。": "Keys, cookies, and real personal information are not shown. Built-in test emails and usernames in official contracts remain as synthetic ground truth.", - "权限与费用": "Access and cost", - "四个自托管站点": "Four self-hosted sites", - "官方 Docker 镜像与内置测试账号;每题前重置": "Official Docker images and built-in test accounts; reset before each task", - "不需要": "Not required", - "模型 API": "Model API", - "需要相应 API 权限": "Corresponding API access required", - "当前团队无权限": "Current team lacks access", - "只发布脱敏静态报告": "Publishes only the sanitized static report", - "发布者需已有站点权限": "Publisher must already have site access", - "字段": "Field", - "任务成绩": "Task score", - "逻辑结果耗时中位数": "Median logical-result latency", - "版本与镜像身份用于限定复现边界,不参与评分。": "Version and image identity bound reproducibility and do not affect scoring.", - "全部逻辑结果": "All logical results", - "每题只展示一个最终逻辑结果;端到端耗时与模型用量包含获得该结果所需的全部执行。": "Each task shows one final logical result. End-to-end latency and model usage include every execution needed to obtain it.", - "Agent 耗时": "Agent latency", - "BrowserTool 耗时": "BrowserTool latency", - "失败原因": "Failure reason", - "程序自动定位": "Automatic failure locator", - "核验根因子类": "Verified root-cause subtype", - "核验原因": "Verified cause", - "最终回答验收失败": "Final-answer acceptance failure", - "网络副作用验收失败": "Network-side-effect acceptance failure", - "多项验收失败": "Multiple acceptance failures", - "60 个任务×浏览器结果均保留最终回答、官方 evaluator 结果、Agent 事件和证据哈希。": "All 60 task-browser results retain the final answer, official evaluator result, agent events, and evidence hashes.", - "60/60 个逻辑结果已覆盖,0 个未决。它不重新判题,也不是独立语义复核。": "Evidence-integrity checks cover all 60 logical results with no unresolved integrity issue. They do not rescore tasks or constitute independent semantic review.", - "60/60 个逻辑结果完成证据完整性审计": "Evidence integrity checked for all 60 logical results", - "每题声明一个结构化最终回答合同,并在需要确认页面副作用时附加一个或多个网络事件合同。所有合同都通过才记 1 分。任务页只解释合同;各浏览器的实际回答、失败规则和 CowAgent 自主轨迹统一放在结果页。": "Each task declares a structured final-answer contract and, where required, one or more network-event contracts for page side effects. Every contract must pass for a score of 1. Task pages explain only the contract; browser answers, failed rules, and CowAgent trajectories appear on result pages.", - "运行器代码在每个执行前调用": "Before each execution, the runner calls", - "先看 30 个同题配对,再定位 10 个失败结果的官方规则、实际回答、轨迹和日志。": "Start with the 30 same-task comparisons, then inspect the official rules, actual answers, trajectories, and logs for the 10 failed results.", - "Agent 链路耗时中位数": "Median agent-stack latency", - "Agent 链路耗时 P95": "P95 agent-stack latency", - "Agent 链路累计耗时": "Cumulative agent-stack latency", - "Agent 链路累计": "agent-stack cumulative", - "BrowserTool 累计耗时": "Cumulative BrowserTool latency", - "BrowserTool 累计": "BrowserTool cumulative", - "把获得该任务结果实际发生的执行相加;每次从浏览器会话准备开始,到 CowAgent 完成、验收所需证据落盘前结束,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": "sums all execution used to obtain the task result. Each execution runs from browser-session preparation until CowAgent completes and acceptance evidence is persisted. It includes sign-in, model inference, orchestration, page waits, BrowserTool, and cleanup, but excludes the preceding Docker site reset.", - "是事件流中全部 browser 工具调用墙钟时间之和,包含工具封装、CDP 往返与页面等待,仍不是纯内核 CPU 时间。": "is the sum of wall-clock durations for all browser tool calls in the event stream. It includes wrapper, CDP round-trip, and page-wait overhead, so it is not pure browser-core CPU time.", - "次调用": "calls", - "次动作": "actions", - "次模型请求": "model requests", - "如何增加下一名选手": "How to add another participant", - "原版 Mind2Web、Online-Mind2Web 与 Mind2Web 2 的任务和评分器不同,不能合并到同一成绩表。": "Original Mind2Web, Online-Mind2Web, and Mind2Web 2 use different tasks and graders and must not be merged into one score table.", - "新增浏览器时,需要注册其容器镜像、版本与 CDP 端点,并在同一冻结任务集和共同条件下形成完整结果。总榜按浏览器逐行扩展;同题矩阵按浏览器增加列;统计比较按浏览器对单独生成,不能把多个浏览器压成一个总胜负。": "Register the new browser's container image, version, and CDP endpoint, then complete the same frozen task set under the shared controls. The leaderboard adds a browser row, the task matrix adds a browser column, and statistical comparisons are generated per browser pair rather than collapsing multiple browsers into one win/loss label.", - "先看总体与同题比较,再通过筛选下钻到每个结果的官方规则、实际回答、轨迹和日志。": "Start with aggregate and same-task comparisons, then use filters to inspect each result's official rules, actual answer, trajectory, and logs.", - "Agent 链路耗时": "Agent-stack latency", - "均值": "Mean", - "总耗时": "Total latency", - "单次均值": "Mean per call", - "调用数": "Call count", - "更优": "Better", - "标记方向明确且数值更优的指标:通过数与通过率越高越优,耗时与 Tokens 越低越优;数值相同时不标记。": "marks metrics with a clear direction and a better value: higher pass counts and rates are better, while lower latency and token usage are better. Ties are not marked.", - "按 CowAgent 发出的 BrowserTool action 聚合总墙钟时间、单次均值与调用数。耗时较低者标记为更优;调用数描述 Agent 行为,不判优。该指标比 Agent 总耗时更接近浏览器执行层,但仍包含工具与 CDP 开销。": "Aggregate total wall-clock time, mean per call, and call count by BrowserTool action emitted by CowAgent. Lower latency is marked as better; call count describes agent behavior and is not ranked. This metric is closer to the browser execution layer than total agent latency but still includes tool and CDP overhead.", - "核验与修订记录": "Review and revision history", - "按轮次记录核验范围、发现、正式处理和遗留事项;单个任务的判定证据位于结果详情页。": "Each round records its scope, findings, formal corrections, and remaining items. Per-task verdict evidence remains on result detail pages.", - "当前状态": "Current status", - "发布门禁:": "Release gate:", - "证据文件、哈希、分数、状态、官方版本与报告链接已通过程序化一致性检查。": "Evidence files, hashes, scores, statuses, the official version, and report links passed programmatic consistency checks.", - "官方 WebArena‑Verified evaluator 是成绩来源;核验记录只说明报告和实现经过了哪些检查与修订,不替代任务判分。": "The official WebArena-Verified evaluator is the scoring authority. This history records checks and revisions to the implementation and report; it does not replace task scoring.", - "核验轮次": "Review rounds", - "核验范围": "Scope", - "发现": "Findings", - "处理": "Corrections", - "遗留事项": "Remaining items", - "已处理": "Resolved", - "无阻塞项。": "No blocking items.", - "多浏览器比较与结果详情核验": "Multi-browser comparison and result-detail review", - "同题比较交互、BrowserTool 动作延迟和单结果详情首屏。": "Same-task comparison interaction, BrowserTool action latency, and the opening section of result-detail pages.", - "同题视图虽动态生成列,但交互仍以两浏览器关系为中心;动作表层级拥挤且对不同调用量的总耗时判优;完整 evaluator 错误被放入窄指标卡。": "The same-task view generated columns dynamically but still centered its interaction on a two-browser relationship; the action table was visually congested and ranked totals with unequal call counts; full evaluator errors appeared in narrow KPI cards.", - "同题比较改为基准浏览器加多选对比浏览器和明确关系条件;动作延迟改为按 action 分卡并只比较单次均值;详情首屏只保留简明规则定位,完整 expected、actual 和错误进入规则证据区。": "Changed same-task comparison to one baseline browser, multi-select comparison browsers, and explicit outcome relations; split action latency into action cards and compare only mean per call; kept only concise rule labels in the result summary and moved complete expected, actual, and error evidence to the rule section.", - "当前仅有两个参评浏览器;扩展行为已由三浏览器测试模型覆盖,新增真实浏览器仍需形成完整同题结果。": "Only two browser participants are currently measured. A three-browser test model covers extensibility behavior, while any additional real browser still requires complete same-task results.", - "参评浏览器 · Browser Use 评测": "Browser participants · Browser Use evaluation", - "被测浏览器": "Browsers tested", - "被测浏览器与固定实验条件": "Browsers tested and fixed experimental controls", - "本报告比较浏览器。每个模型单独统计,CowAgent、任务、资源上限、网站和 evaluator 均保持一致。": "This report compares browsers. Results are calculated separately for each model, while CowAgent, tasks, resource limits, sites, and evaluators remain fixed.", - "浏览器与版本": "Browsers and versions", - "浏览器": "Browser", - "通过任务": "Tasks passed", - "每个模型分别比较各浏览器,不把不同模型的结果合并排名。": "Browsers are compared separately within each model; results from different models are not merged into one ranking.", - "增加新的浏览器": "Adding another browser", - "仓库中的 Browser Trajectory Replay(轨迹重放)是另一项固定动作测试,不计入这里的成绩。": "The repository's Browser Trajectory Replay track is a separate fixed-action test and is not included in these scores.", - "评测结果": "Evaluation results", - "浏览器 B": "Browser B", - "同一任务上的结果差异": "Same-task outcome differences", - "每行比较同一模型、同一任务和同一网站状态下的两个浏览器。A、B 对应表内所列浏览器。": "Each row compares two browsers under the same model, task, and site state. A and B identify the browsers shown in that row.", - "三类任务的覆盖与成绩": "Coverage and results by task type", - "信息提取、页面导航和页面操作使用不同验收合同,因此分类型展示,不把难度或任务类型混成新的加权分。": "Information retrieval, navigation, and page changes use different acceptance contracts, so they are reported separately without creating a new weighted score.", - "数据是否齐全": "Data completeness", - "检查项": "Check", - "每个失败结果都有分类和具体原因": "Every failed result has a category and a specific cause", - "完整性检查没有发现待处理项": "The integrity check found no unresolved issue", - "包含正式执行及按协议发生的一次重试": "Includes the main execution and the single retry allowed by the protocol", - "总体成绩和耗时": "Overall results and latency", - "报告分别使用 GPT-5.6 Sol 和 GPT-6 Astra 驱动 CowAgent。每组都在相同任务、网站和验收规则下比较浏览器。": "CowAgent was driven by GPT-5.6 Sol and GPT-6 Astra. Within each model run, browsers were compared on the same tasks, sites, and acceptance rules.", - "WebArena‑Verified 1.2.3 · 官方程序化 evaluator · 不使用 LLM judge": "WebArena-Verified 1.2.3 · official programmatic evaluator · no LLM judge", - "在 Sol 运行中,Chromium 比 Moli 多通过 3 题;在 Astra 运行中,两者都是 71/90。耗时需要结合下表和逐题记录看:Agent 总耗时还包含模型推理与页面等待,BrowserTool 耗时也包含工具封装和 CDP 往返。": "In the Sol run, Chromium passed three more tasks than Moli; in the Astra run, both passed 71/90. Latency must be read with per-task records: agent total time includes model inference and page waits, while BrowserTool time still includes tool wrapping and CDP round trips.", - "只标记同一模型下数值唯一更好的浏览器。通过数越高越好;耗时和 Tokens 越低越好。Agent 总耗时包含模型推理、页面等待和浏览器操作,BrowserTool 耗时更接近浏览器执行层,但仍包含工具封装与 CDP 往返。": "marks only the uniquely better browser within the same model. Higher pass counts are better; lower latency and token use are better. Agent total time includes model inference, page waits, and browser actions. BrowserTool time is closer to the browser execution layer but still includes tool wrapping and CDP round trips.", - "打开逐题对照 →": "Open the task-by-task comparison →", - "官方 evaluator 说明哪条验收规则没有满足;下表来自对任务要求、最终回答、网络记录和自主执行轨迹的逐条核验。": "The official evaluator identifies the unmet acceptance rule. The table comes from reviewing each task requirement, final answer, network record, and autonomous trajectory.", - "查看逐条核验原因 →": "View every verified cause →", - "作为分层变量为各浏览器生成决策": "Generates browser decisions within each separately reported model run", - "从模型分层的浏览器对照读出下一轮该怎么做": "What the browser comparisons imply for the next run", - "步骤预算耗尽": "Step budget exhausted", - "导航语义错误": "Navigation-semantics error", - "检索覆盖不足": "Incomplete retrieval coverage", - "商品选择错误": "Incorrect product selection", - "候选比较错误": "Candidate-comparison error", - "提交字段错误": "Submission-field error", - "检索策略错误": "Retrieval-strategy error", - "流程未完成": "Incomplete workflow", - "结果去重遗漏": "Result-deduplication failure", - "可恢复错误后提前终止": "Premature stop after recoverable error", - "批量操作遗漏": "Incomplete batch workflow", - "提交字段遗漏": "Submission-field omission", - "路由验收缺陷": "Route-acceptance defect", - "evaluator 实现错误": "Evaluator implementation defect", - "任务与验收矛盾": "Instruction-evaluator contradiction", - "表单验收缺陷": "Form-acceptance defect", - "任务状态与验收矛盾": "State-evaluator contradiction", - "站点保存故障": "Site save failure", - "站点依赖故障": "Site dependency failure", - "站点会话故障": "Site session failure", - "网络证据缺失": "Missing network evidence", - "网络证据匹配缺陷": "Network-evidence matching defect", - "响应证据缺失": "Missing response evidence", - "共享环境并发污染": "Shared-environment concurrency contamination", - "检索遗漏与参考状态差异": "Retrieval omission and reference-state drift", - "同题比较只在同一模型内进行。A、B 是表内所列的两个浏览器。": "Same-task comparisons are made only within one model. A and B identify the two browsers listed in the row.", - "每行只比较同一模型、同一任务和同一网站状态下的结果。": "Each row compares results under the same model, task, and site state.", - "两个模型的结果不能直接当成模型排名": "The two model runs are not a model ranking", - "Sol 与 Astra 在不同时间、不同代码提交上运行。Astra 的两个 Task 77 结果还受到已确认的共享环境并发污染。表格可以帮助观察差异,但不能把差值全部归因于模型。": "Sol and Astra ran at different times and on different code revisions. Two Astra Task 77 results were also affected by confirmed concurrent contamination of shared site state. The table shows observations, but the differences cannot all be attributed to the model.", - "evaluator 的报错只说明哪条验收规则没有满足。我们继续核对任务要求、最终回答、网络记录和 Agent 轨迹,确认失败发生在 Agent、任务规则、测试网站、共享环境或浏览器证据记录中的哪一处。": "Evaluator errors identify the unmet acceptance rule. The review then checks the task requirement, final answer, network record, and agent trajectory to locate the failure in the agent, task contract, test site, shared environment, or browser evidence capture.", - "查看每个失败结果的核验结论": "View the verified cause for every failed result", - "人工核验用于解释官方失败结果,不修改分数。修复后需要重新运行才能产生新成绩。": "Review explains official failures without changing scores. A fix affects results only after the task is run again.", - "样本覆盖简单、中等和困难任务": "The sample covers short, medium, and long tasks", - "为什么使用 WebArena‑Verified": "Why WebArena-Verified was selected", - "原版 Mind2Web 主要检查离线动作预测。Online‑Mind2Web 和 WebVoyager 使用实时网页,网页内容会变化,部分评分还依赖模型裁判。WebArena‑Verified 可以本地部署、逐题重置,并用最终答案和网络副作用做程序化验收,适合只替换浏览器的对照实验。": "The original Mind2Web mainly tests offline action prediction. Online-Mind2Web and WebVoyager use live pages, where content drifts and some scoring relies on model judges. WebArena-Verified can be self-hosted and reset per task, with programmatic checks of final answers and required network side effects, which suits a browser-only comparison.", - "它的代价是部署较重,环境故障也会影响结果;官方 evaluator 不评价操作路径是否高效,也覆盖不了开放互联网的全部情况。": "Its costs are a heavier deployment and exposure to environment failures. The official evaluator does not score path efficiency or cover the full open web.", - "这组结果能说明什么": "What these results support", - "下一步": "Next step", - "按核验结论分别修复 Agent、评测任务、测试网站和浏览器证据记录,再重新运行受影响的任务;新增浏览器时继续使用同一组任务和验收规则。": "Fix the agent, benchmark tasks, test sites, and browser evidence capture according to the verified causes, then rerun affected tasks. New browsers should use the same task set and acceptance rules.", - "Chromium 与 Moli 作为浏览器后端参与同题对照;Agent、模型、任务、资源上限、站点与 evaluator 保持一致。": "Chromium and Moli participate as browser backends in a same-task comparison; the agent, model, tasks, resource limits, sites, and evaluators are held constant.", - "参评浏览器是本轮唯一实验变量;完整 Agent 栈的其余组成列为统一实验条件。": "The browser participant is the only experimental variable in this run; all other components of the agent stack are shared controls.", - "逻辑结果耗时 P50": "P50 logical-result latency", - "WebArena‑Verified 当前提供 Shopping、Shopping Admin、Reddit、GitLab、Wikipedia、Map 六类环境。本轮 30 题只覆盖前四类;Wikipedia 与 Map 需要额外大型数据卷,因此没有部署,也没有任务进入成绩。": "WebArena-Verified currently provides six environments: Shopping, Shopping Admin, Reddit, GitLab, Wikipedia, and Map. This 30-task run covers only the first four. Wikipedia and Map require additional large data volumes, so they were not deployed and contribute no scored tasks.", - "只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。当前冻结证据保存了代码提交、运行协议和镜像身份;事件流从环境准备完成后开始,因此逐次 reset、ready 与 health 结果未形成独立绑定的准备回执。新版运行协议已要求保存该回执。": "and keeps only the sites required by the task. After env-ctrl reports ready, it performs repeated application-health checks, clears browser state, signs in with test accounts, and opens the start page. The frozen evidence records the code commit, protocol, and image identities; events begin after preparation, so per-execution reset, ready, and health outcomes do not have independently bound preparation receipts. The current protocol now requires that receipt.", - "报告模型与比较口径核验": "Report model and comparison review", - "统计摘要、优胜表达、结果下钻、核验记录和参评浏览器说明。": "Summary statistics, winner presentation, result drill-down, review history, and browser-participant description.", - "统计标签重复表达 P50;比较表缺少方向一致的优胜标记;结果页存在重复失败清单;核验页承担了逐结果清单职责。": "The statistics duplicated P50 terminology; comparison tables lacked directionally consistent winner marks; the results page duplicated the failure inventory; and the review page carried a per-result listing.", - "统一为均值、P50、P95;按指标方向突出唯一优胜者;删除重复失败清单;核验页改为按轮次记录范围、发现、处理和遗留事项。": "Standardized on mean, P50, and P95; marked unique winners by metric direction; removed the duplicate failure inventory; and changed the review page to round-based scope, findings, corrections, and remaining items.", - "当前固定样本没有浏览器单边通过任务;优胜标记主要反映耗时与 Tokens,不改变官方任务成绩。": "The fixed sample has no browser-only pass. Winner marks therefore mainly reflect latency and token usage and do not change official task scores.", - "部署证据与计时边界核验": "Deployment evidence and timing-boundary review", - "本地站点部署、Colima 网络路径、环境准备证据、Agent 与 BrowserTool 计时。": "Local site deployment, Colima network path, environment-preparation evidence, and Agent/BrowserTool timing.", - "环境拓扑与网关含义缺少集中说明;历史总耗时边界描述不准确;逐执行环境准备没有独立回执。": "The environment topology and gateway lacked a consolidated explanation; the historical total-latency boundary was inaccurate; and per-execution environment preparation had no independent receipt.", - "补充部署流程图、端口和镜像身份;区分 Agent 链路与 BrowserTool 墙钟时间;新版 runner 保存环境准备和分阶段计时。": "Added the deployment flow, ports, and image identities; separated Agent-stack and BrowserTool wall time; and made the current runner persist environment preparation and phase timings.", - "本批冻结结果不具备逐执行准备回执,报告仅按现有证据声明可复现边界。": "This frozen run lacks per-execution preparation receipts, so the report states reproducibility boundaries only to the extent supported by existing evidence.", - "运行器与证据完整性核验": "Runner and evidence-integrity review", - "模型响应校验、BrowserTool 清理、HAR 留存、证据目录和恢复执行。": "Model-response validation, BrowserTool cleanup, HAR retention, evidence directories, and resume behavior.", - "异常清理可能遮蔽原始错误;HAR 停止操作缺少幂等保护;异常响应和原始证据目录需要更严格保护。": "Cleanup exceptions could mask primary failures; HAR stop lacked idempotency; and malformed responses and raw-evidence directories needed stricter protection.", - "隔离清理异常、使 HAR 停止幂等、加强模型响应结构校验,并阻止原始浏览器证据进入 Git。": "Isolated cleanup failures, made HAR stop idempotent, strengthened model-response validation, and prevented raw browser evidence from entering Git.", - "统一比较统计与核验记录": "Standardize comparison statistics and review history", - "统一比较统计与可扩展浏览器对比": "Standardize comparison statistics and scalable browser comparison", - "比较摘要统一展示均值、P50、P95;同题比较采用基准浏览器与多选浏览器组合;动作延迟按 action 分卡并只对单次均值判优;结果详情将长 evaluator 证据移出指标卡。Chromium 与 Moli 的官方成绩仍均为 25/30。": "Comparison summaries now show mean, P50, and P95. Same-task comparison uses one baseline browser and multiple comparison browsers. Action latency is grouped by action and ranks only mean per call, while long evaluator evidence has moved out of result KPI cards. Official scores remain 25/30 for both Chromium and Moli.", - "比较摘要统一展示均值、P50、P95,并按指标方向突出唯一优胜者;结果页删除与筛选结果重复的失败清单;核验页改为按轮次记录核验与修订。Chromium 与 Moli 的官方成绩仍均为 25/30。": "Comparison summaries now show mean, P50, and P95 and mark unique winners according to metric direction. The results page no longer duplicates the filterable failure inventory, and the review page records review and revision rounds. Official scores remain 25/30 for both Chromium and Moli.", - "统计摘要": "Summary statistics", - "耗时和 Tokens 的集中趋势与尾部表现未采用统一展示结构。": "Latency and token central tendency and tail behavior did not use a consistent presentation.", - "每个指标统一展示均值、P50、P95;P50 不再以另一个名称重复出现。": "Each metric now shows mean, P50, and P95; P50 is no longer duplicated under another label.", - "只改变统计展示,官方任务分数、分母和证据不变。": "Only statistical presentation changed; official task scores, denominators, and evidence are unchanged.", - "比较表达": "Comparison presentation", - "读者需要逐格判断数值方向。": "Readers previously had to infer metric direction cell by cell.", - "通过数与通过率按更高值标优,耗时与 Tokens 按更低值标优;平局和调用次数保持中性。": "Higher pass counts and rates and lower latency and token usage are marked as better; ties and call counts remain neutral.", - "优胜标记是描述性比较,不改变任务判定。": "Winner marks are descriptive comparisons and do not change task verdicts.", - "页面职责": "Page responsibilities", - "失败清单与结果筛选重复,核验页混入逐结果覆盖表。": "The failure inventory duplicated result filters, and the review page included a per-result coverage table.", - "结果库保留成功/失败筛选和任务详情;核验页只呈现核验轮次、处理和遗留事项。": "The result library retains pass/fail filters and task details; the review page now contains only review rounds, corrections, and remaining items.", - "减少重复内容,不删除结果证据。": "Duplicate presentation was removed without deleting result evidence.", - "多浏览器对比": "Multi-browser comparison", - "同题矩阵会随浏览器数量横向增宽,关系筛选也无法表达一个基准浏览器与多个对比浏览器的组合。": "The same-task matrix widened with every browser, and its relationship filter could not express one baseline browser against multiple comparison browsers.", - "改为基准浏览器单选、对比浏览器多选和明确结果关系;任务行按通过与失败浏览器分组,浏览器标签链接完整结果。": "Changed to one baseline browser, multi-select comparison browsers, and explicit outcome relations. Task rows group passing and failing browsers, with browser labels linking to complete results.", - "页面结构可直接容纳新增浏览器;当前结果和分数不变。": "The page structure now accepts additional browsers directly; current results and scores are unchanged.", - "动作延迟与详情页": "Action latency and result details", - "动作表把三项指标压入浏览器单元格,并对调用量不同的总耗时判优;详情首屏的长错误文本破坏阅读层级。": "The action table compressed three metrics into each browser cell and ranked totals despite unequal call counts; long error text disrupted the hierarchy of result-detail pages.", - "每个 action 使用独立比较卡,只以单次均值判优并将总耗时、调用数保持中性;详情首屏只显示简明失败规则,完整诊断保留在逐规则证据区。": "Each action now has a dedicated comparison card. Only mean per call is ranked, while total latency and call count remain neutral. Result summaries show concise failed-rule labels, with complete diagnostics retained in rule-level evidence.", - "修正比较解释与页面层级,不改变测量值、判分或证据。": "This corrects comparison interpretation and page hierarchy without changing measurements, verdicts, or evidence.", - } + dict(_TRANSLATIONS['TEXT_2']) ) # Report-authored interface copy introduced by the multi-model renderer. Task @@ -1078,128 +69,7 @@ # deliberately not covered here: they are measured material and may remain in # their source language. TEXT.update( - { - "Browser Use 联合多模型能力实测": "Browser Use capability evaluation across model cohorts", - "浏览器 Browser Use 能力对比": "Browser capability comparison on Browser Use tasks", - "先看结论": "Key findings", - "失败根因是报告解读的核心证据": "Failure root causes are core interpretation evidence", - "任务族覆盖": "Task-family coverage", - "单边通过": "One-browser-only passes", - "共同失败": "Both browsers failed", - "不等于共同根因": "Not necessarily the same root cause", - "模型内完整配对": "complete within-model pairs", - "本轮共有 9 个模型内不一致配对,且方向随模型层变化;固定样本不支持对全部 Browser Use 任务作统计外推。": "This run contains nine discordant within-model pairs, and their direction changes across model cohorts. The fixed sample does not support statistical generalization to all Browser Use tasks.", - "浏览器后端是参评选手;模型是分层实验变量,每个模型层内保持 Agent、任务、资源上限、站点与 evaluator 一致。": "Browser backends are the participants. Models are stratification variables; within each model cohort, the agent, tasks, resource limits, sites, and evaluators are held constant.", - "官方任务身份、公开意图与程序化验收合同。运行结果位于结果分析,不混入评测集定义。": "Official task identity, public intent, and programmatic acceptance contract. Measured outcomes appear in Results and are kept separate from the dataset definition.", - "查看本题运行结果": "View measured results for this task", - "评测集页面保持为冻结输入;实测输出、判分和轨迹在结果页查看。": "Dataset pages remain frozen inputs. Measured outputs, verdicts, and trajectories appear on result pages.", - "Chromium 结果": "Chromium results", - "Moli 1.1.3 结果": "Moli 1.1.3 results", - "全部浏览器对照": "Compare all browsers", - "程序自动定位:": "Automatic failure locator:", - "核验根因子类:": "Reviewed root-cause subtype:", - "核验原因:": "Reviewed cause:", - "查看修正依据、处理与影响": "View findings, treatment, and impact", - "每个版本只记录影响读者判断的正式修正;结论、分母、耗时与证据影响直接展示,技术依据按需展开。": "Each release records only formal corrections that affect reader interpretation. Effects on conclusions, denominators, latency, and evidence are shown directly, with supporting details available on demand.", - "判分权威": "Scoring authority", - "物理执行": "Physical execution", - "模型层": "Model cohort", - "正式处理": "Formal treatment", - "未命名版本": "Untitled release", - "未命名核验": "Untitled review", - "官方 Hard": "Official hard subset", - "步起": "minimum steps", - "模板 ID": "template ID", - "公开文件": "Public files", - "没有 evaluator 明细。": "No evaluator details are available.", - "没有可展示的 BrowserTool 调用。": "No BrowserTool calls are available to display.", - "最终回答 JSON": "Final answer JSON", - "完整执行证据 JSON": "Complete execution evidence JSON", - "Evaluator 明细 JSON": "Evaluator details JSON", - "动作轨迹 JSON": "Action trajectory JSON", - "脱敏事件日志 JSON": "Sanitized event log JSON", - "原始证据哈希": "Source-evidence hashes", - "返回任务页": "Return to the task page", - "任务与验收": "Task and acceptance", - "执行轨迹": "Execution trajectory", - "运行与证据": "Runtime and evidence", - "查看任务合同": "View task contract", - "查看任务与验收": "View task and acceptance", - "基准浏览器": "Baseline browser", - "对比浏览器": "Comparison browsers", - "完整配对": "Complete pairs", - "仅基准通过": "Baseline only", - "仅对比通过": "Comparison only", - "模型内浏览器配对结果": "Within-model browser pair outcomes", - "失败根因以当前证据绑定的逐结果核验为准": "Failure causes follow the per-result reviews bound to the current evidence", - "37/37 个失败结果已逐个绑定当前证据并完成核验。": "All 37 failed results are bound to the current evidence and have completed root-cause review.", - "查看逐结果根因与证据边界 →": "View per-result causes and evidence boundaries →", - "220/220 个物理执行均记录 reset、ready、应用健康检查和登录准备状态;事件流随后证明 CowAgent 自主产生 BrowserTool 调用序列。": "All 220 physical executions record reset, readiness, application-health, and sign-in preparation status. The subsequent event streams show CowAgent generating BrowserTool calls autonomously.", - ",只保留该题所需站点;env-ctrl 报告 ready 后,再做连续应用健康检查、清浏览器状态、登录测试账号并打开起始页。本轮 220/220 个物理执行都保存了环境准备回执,并与对应执行记录绑定。": ", keeping only the sites required by the task. After env-ctrl reports ready, the runner performs repeated application-health checks, clears browser state, signs in with test accounts, and opens the start page. All 220 physical executions retain preparation receipts bound to their execution records.", - "本轮 220/220 个物理执行均保存环境准备回执;公开环境摘要保留逐项状态,原始登录态不发布。": "All 220 physical executions retain environment-preparation receipts. The public environment summary preserves each status while authenticated session state remains private.", - "各模型任务成绩": "Task scores by model cohort", - "各模型逻辑结果耗时 P50": "P50 logical-result latency by model cohort", - "判分权威执行的原始证据绑定": "Source-evidence binding for the authoritative execution", - "Chromium 与 Moli 的 Browser Use 能力对比": "Chromium vs Moli on Browser Use tasks", - "评测结果": "Results", - "同一任务上的结果差异": "Outcomes on the same tasks", - "三类任务的覆盖与成绩": "Coverage and results by task type", - "数据是否齐全": "Data completeness", - "Agent 总耗时": "Agent total time", - "同题数": "Matched tasks", - "两边都通过": "Both passed", - "A 通过、B 失败": "A passed, B failed", - "A 失败、B 通过": "A failed, B passed", - "两边都失败": "Both failed", - "原因所在": "Cause area", - "失败结果数": "Failed results", - "具体分类": "Detailed categories", - "说明": "Explanation", - "Agent 决策或操作": "Agent decisions or actions", - "任务或 evaluator": "Task or evaluator", - "测试网站": "Test website", - "浏览器证据记录": "Browser evidence capture", - "共享测试环境": "Shared test environment", - "多项原因": "Multiple causes", - "检索不完整、候选选择错误、走错页面、字段遗漏,或没有在步骤上限内完成。": "Incomplete retrieval, incorrect candidate selection, wrong navigation, missing fields, or failure to finish within the step limit.", - "任务要求与验收条件冲突,或 evaluator 的路由、表单和实现规则有缺陷。": "The task conflicts with its acceptance conditions, or the evaluator has route, form, or implementation defects.", - "自托管网站的依赖、会话或保存接口失败。": "A dependency, session, or save endpoint failed in a self-hosted test site.", - "页面操作可能成功,但网络记录缺少响应正文或 Referer,evaluator 无法确认。": "The page action may have succeeded, but missing response bodies or Referer fields prevented evaluator confirmation.", - "并发运行改写了同一网站状态;运行器现已串行化,历史结果仍按原证据保留。": "Concurrent runs changed shared site state. The runner now serializes execution; historical results remain bound to their original evidence.", - "同一结果同时包含 Agent 遗漏和冻结参考状态差异。": "The same result includes both an Agent omission and a difference from the frozen reference state.", - "任务类型": "Task type", - "测什么": "What it measures", - "信息提取": "Information retrieval", - "页面导航": "Page navigation", - "页面操作": "State-changing action", - "从页面读取、筛选或汇总信息,不修改站点状态": "Read, filter, or summarize page information without changing site state", - "打开符合指定条件的页面或列表": "Open a page or list matching the requested conditions", - "提交表单、修改记录、结账等会改变站点状态的任务": "Submit forms, edit records, check out, or perform other state-changing actions", - "检查项": "Check", - "任务结果": "Task results", - "失败原因核验": "Failure-cause review", - "未解决的证据问题": "Unresolved evidence issues", - "至少成功过一次的任务": "Tasks with at least one successful run", - "保存的实际执行": "Saved executions", - "每个失败结果都有分类和具体原因": "Every failed result has a category and a specific reviewed cause", - "完整性检查没有发现待处理项": "Integrity checks found no unresolved items", - "包含正式执行及按协议发生的一次重试": "Includes score-bearing executions and the one retry permitted by the protocol", - "报告解读": "Interpretation", - "环境与复现": "Environment and reproduction", - "总体成绩和耗时": "Overall scores and latency", - "同一任务上的结果": "Outcomes on the same tasks", - "两个模型的结果不能直接当成模型排名": "The two model runs are not a model ranking", - "失败原因": "Failure causes", - "样本覆盖简单、中等和困难任务": "The sample covers short, medium, and long interaction paths", - "为什么使用 WebArena‑Verified": "Why WebArena-Verified", - "这组结果能说明什么": "What these results support", - "下一步": "Next steps", - "结果编号": "Result ID", - "原因分类": "Cause category", - "核验结论": "Reviewed cause", - "查看每个失败结果的核验结论": "View the reviewed cause for every failed result", - "人工核验用于解释官方失败结果,不修改分数。修复后需要重新运行才能产生新成绩。": "Human review explains official failures without changing scores. A fix requires a new run to produce a new result.", - } + dict(_TRANSLATIONS['TEXT_3']) ) @@ -1241,467 +111,18 @@ def _register_report_change_translations() -> None: _register_report_change_translations() TEXT.update( - { - # Project-authored task labels/rationales. The adjacent official task - # intent remains untranslated measurement input. - "打开一个需要登录的 GitLab 目标页": "Open an authenticated GitLab target page", - "打开一份报表并读取第一个条目": "Open a report and read its first entry", - "打开评论列表并读取一种状态的数量": "Open a review list and read the count for one status", - "搜索一张发票并读取总金额": "Search for an invoice and read its total", - "进入一个论坛、选择最新帖子并点赞": "Enter a forum, choose the newest post, and upvote it", - "进入项目议题列表并应用排序和状态筛选": "Open a project's issue list and apply sorting and status filters", - "把需求转成商品查询并打开匹配详情页": "Turn a requirement into a product query and open the matching detail page", - "进入订单历史,经翻页或排序后读取最早日期": "Open order history and read the earliest date after paging or sorting", - "找到项目贡献者活动页并限定一个日期": "Find a project's contributor activity page and constrain it to one date", - "打开个人项目并检查星标与项目标识": "Open a personal project and inspect its star and project identifier", - "按标题和更新时间筛选个人议题,再读取状态": "Filter personal issues by title and update time, then read status", - "按库存量筛选后台表格并收集 SKU": "Filter an admin table by inventory and collect SKUs", - "按状态筛选订单,打开最新一单并读取总额": "Filter orders by status, open the newest order, and read its total", - "筛选并排序订单,再打开最新的匹配客户": "Filter and sort orders, then open the newest matching customer", - "打开个人资料设置,修改简介并保存": "Open profile settings, edit the biography, and save", - "找到最新论坛帖子,并按作者和票数条件检查多条评论": "Find the newest forum post and inspect multiple comments by author and vote criteria", - "检查一整年的分页订单并汇总数量和支出": "Inspect a full year of paginated orders and total their count and spend", - "检查十个排序帖子,并根据描述归类推荐内容": "Inspect ten sorted posts and classify recommendations from their descriptions", - "检查十个排序帖子,并从合格描述中提取两个字段": "Inspect ten sorted posts and extract two fields from qualifying descriptions", - "应用日期和状态条件,并汇总五个月度数量": "Apply date and status conditions and total five monthly counts", - "应用日期和状态条件,并汇总十一个月度数量": "Apply date and status conditions and total eleven monthly counts", - "搜索商品、检查结果页并计算价格区间": "Search products, inspect the result page, and calculate a price range", - "结合搜索词排名与独立的商品可用性检查": "Combine query ranking with an independent product-availability check", - "检查日期区间内的订单、归类商品并汇总行项目金额": "Inspect orders in a date range, classify products, and total line-item amounts", - "筛选库存、检查变体并返回成对商品字段": "Filter inventory, inspect variants, and return paired product fields", - "排序已完成订单、检查两份详情并合计付款": "Sort completed orders, inspect two details, and total payments", - "检查两组各四笔订单,并计算付款差额": "Inspect two groups of four orders and calculate the payment difference", - "找到最新完成订单,并规范化、排序多个行项目": "Find the latest completed order, then normalize and sort multiple line items", - "清空购物车,按预算和评分筛选品类,选择商品并结账": "Empty the cart, filter a category by budget and rating, choose a product, and check out", - "定位项目文件、修改标题、提交到默认分支并验证": "Locate a project file, change its title, commit to the default branch, and verify", - # Standalone interpretation article. - "报告解读 · Browser Use 评测": "Interpretation · Browser Use evaluation", - "从 25 比 25 读出下一轮该怎么做": "What the 25-to-25 result implies for the next run", - "完整文章与首页共用同一份测量数据;另提供无 JavaScript 的独立导出版。": "The full article and home page use the same measured data. A standalone JavaScript-free export is also available.", - "独立导出": "Standalone export", - "打开独立文章": "Open standalone article", - "下载论点证据": "Download claim evidence", - "Browser Use 实测解读": "Browser Use evaluation interpretation", - "25 比 25 不是“内核等价”:当前样本没有产生分歧": "25 to 25 does not mean browser-core equivalence: this sample produced no disagreement", - "同一 CowAgent 与同一模型替换浏览器后,30 个配对任务没有一个出现单边胜出。这个结果证明对照流程跑通,却没有为内核优劣提供方向性证据。": "With the same CowAgent and model and only the browser changed, none of the 30 paired tasks produced a one-browser-only win. The controlled workflow ran successfully, but the result provides no directional evidence about browser-core superiority.", - "测量口径:WebArena‑Verified 1.2.3;每题×浏览器一个最终逻辑结果;官方确定性 evaluator;无 LLM judge。": "Measurement unit: WebArena-Verified 1.2.3; one final logical result per task-browser pair; official deterministic evaluators; no LLM judge.", - "一、最重要的结果不是平分,而是零个不一致配对": "1. The key result is not the tie but the absence of discordant pairs", - "Chromium 与 Moli 分别通过 25/30 和 25/30。逐题配对后,25 题共同通过、5 题共同失败,两个浏览器单边通过都是 0。由于固定样本里没有不一致任务,这批数据不能估计浏览器的方向性效应,也不对 WebArena‑Verified 全集作统计外推。": "Chromium and Moli each passed 25/30. Pairing by task gives 25 joint passes, five joint failures, and zero one-browser-only passes. Because this fixed sample contains no discordant tasks, it cannot estimate a directional browser effect or support statistical generalization to the full WebArena-Verified set.", - "表 1|30 任务的逐题最终结果配对。": "Table 1 | Paired final results for 30 tasks.", - "可支持的判断": "Supported conclusion", - "当前组合可完成": "Current stack can complete", - "严谨表述只能是“本轮没有观察到成功率差异”,不能写成“两种内核等价”。30 题对小效应检出力有限;更关键的是,样本没有诱发内核分歧。": "The defensible statement is only that this run observed no success-rate difference, not that the browser cores are equivalent. Thirty tasks have limited power for small effects; more importantly, this sample did not elicit browser-core disagreement.", - "二、共同失败是待核验线索,不是已证实根因": "2. Shared failures are leads to verify, not confirmed root causes", - "5 个共同失败任务均未满足官方验收合同。下表只陈述官方expected与actual结果的差异,不把差异直接归因给 Agent、浏览器或站点。": "All five shared-failure tasks missed the official acceptance contract. The table states only the difference between official expected and actual results and does not directly attribute it to the agent, browser, or site.", - "表 2|任务详情公开官方合同、actual回答、逐规则判定与执行轨迹。": "Table 2 | Task details expose the official contract, actual answer, per-rule verdicts, and execution trajectory.", - "公开诊断": "Published diagnosis", - "官方expected包含多本书;四次执行的actual回答都只包含一本。": "The official expected value contains multiple books; the actual answer in all four executions contains only one.", - "actual回答中的价格范围与该题官方目标商品范围不一致。": "The price range in the actual answer differs from the official target-product range.", - "actual订单汇总与官方expected值不一致。": "The actual order aggregate differs from the official expected value.", - "actual回答为空列表,而官方expected为非空项目列表。": "The actual answer is an empty list, while the official expected value is a non-empty project list.", - "官方网络副作用合同未通过;购物车最终状态未满足题目要求。": "The official network-side-effect contract failed; the final cart state did not satisfy the task.", - "这五题在本轮没有任何successful trajectory,因此当前站点快照的可完成性也未被本报告独立证明。下一步应先做不参与计分的人工或 oracle 可完成性核验,再区分 Agent、工具抽象、步数上限、站点状态和浏览器因素。": "None of these five tasks has a successful trajectory in this run, so the report does not independently establish solvability on the current site snapshot. The next step is a non-scoring human or oracle solvability check, followed by separating agent, tool abstraction, step-limit, site-state, and browser factors.", - "三、简单、中等、困难都覆盖了,但“难度”只是操作长度": "3. Easy, medium, and hard are covered, but difficulty means operation length", - "WebArena‑Verified 没有官方 easy/medium/hard 三档。本项目在运行前按最少可见 UI 操作数注册为 1–4、5–7、8+ 三档,配额 5/10/15。它控制操作链覆盖,不等同于推理难度。": "WebArena-Verified has no official easy/medium/hard tiers. Before execution, this project registered tasks by minimum visible UI actions as 1-4, 5-7, and 8+, with quotas of 5/10/15. This controls operation-chain coverage and is not equivalent to reasoning difficulty.", - "表 3|各层级最终通过数/任务数。": "Table 3 | Final passes/tasks by tier.", - "本项目分层": "Project tier", - "表 4|四个可重置自托管站点。": "Table 4 | Four resettable self-hosted sites.", - "四、选择 WebArena‑Verified 是为了归因,不是因为它最流行": "4. WebArena-Verified was chosen for attribution, not popularity", - "Mind2Web 家族采用面更广,但原版主要评离线动作预测;Online‑Mind2Web 和 WebVoyager贴近实时网页,却引入网页漂移和模型裁判。WebArena‑Verified 可以自托管、逐题重置,并用结构化最终答案和必要网络副作用做程序化验收,更适合“只替换浏览器”的 A/B。": "The Mind2Web family has broader adoption, but its original version mainly evaluates offline action prediction. Online-Mind2Web and WebVoyager are closer to live browsing but introduce page drift and model judging. WebArena-Verified is self-hosted, resettable per task, and programmatically evaluates structured final answers and required network side effects, making it better suited to a browser-only A/B test.", - "代价也明确:部署重,环境兼容本身会成为工程变量;官方 evaluator 不衡量路径效率,也不能覆盖开放互联网泛化。": "The tradeoff is substantial deployment, with environment compatibility becoming an engineering variable. The official evaluator does not measure path efficiency or open-web generalization.", - "五、下一轮应主动寻找内核敏感样本": "5. The next run should actively seek browser-sensitive tasks", - "保留 CowAgent、模型、reasoning=low 和每题最多两次执行的固定协议,扩大稳定可验收的配对样本;新增长页面、上传下载、复杂编辑器、弹窗、多标签页等更可能诱发内核差异的任务。实时网站另开 Online‑Mind2Web / WebVoyager 泛化轨道,LLM judge 与人工抽检单列。": "Keep the fixed CowAgent, model, reasoning=low, and at-most-two-executions protocol while expanding the stable, evaluable paired sample. Add long pages, uploads/downloads, complex editors, dialogs, multiple tabs, and other tasks more likely to elicit browser differences. Use a separate Online-Mind2Web / WebVoyager generalization track for live sites, with LLM judging and human spot checks reported separately.", - "决策边界": "Decision boundary", - "本轮支持“流程可比、当前样本无方向性差异”;不支持“内核等价”“Moli 已全面替代 Chromium”或“端到端耗时就是纯内核速度”。Astra 获权后必须形成独立运行。": "This run supports a comparable workflow and no directional difference in the current sample. It does not support browser-core equivalence, full replacement of Chromium by Moli, or interpreting end-to-end latency as pure browser-core speed. Astra requires a separate run once access is available.", - } + dict(_TRANSLATIONS['TEXT_4']) ) TEXT.update( - { - "四种配置的总体结果": "Overall results across four configurations", - "四种配置总览": "Four-configuration overview", - "四种配置 · 单次调用墙钟时间": "Four configurations · per-call wall time", - "全部配置 · 单次调用墙钟时间": "All configurations · per-call wall time", - "全部配置中单次均值最低": "Lowest mean per-call latency across all configurations", - "按模型分层 · 单次调用墙钟时间": "Within each model cohort · per-call wall time", - "同模型浏览器中单次均值最低": "Lowest per-call mean within this model cohort", - "同模型浏览器中通过数最高": "Highest pass count within this model cohort", - "同模型浏览器中通过率最高": "Highest success rate within this model cohort", - "只在同一模型的两个浏览器之间标记唯一较优值;相同值不标。跨模型批次不判优。": "A unique better value is marked only between browsers in the same model cohort. Ties are unmarked, and model cohorts are not ranked against each other.", - "四种配置均使用同一组 90 题,但两个模型层的运行日期和 browser‑eval 提交不同。": "All four configurations use the same 90 tasks, but the model cohorts ran on different dates and browser-eval commits.", - "每个动作按模型分层比较两个浏览器的单次调用墙钟均值,只标记层内唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "For each action, the two browsers are compared within each model cohort by mean per-call wall time, and only a unique cohort minimum is marked. Total time and call count depend on agent workload and provide context rather than a speed ranking.", - "每个动作把全部模型×浏览器配置放在一起比较单次调用墙钟均值,只标记全局唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "For each action, every model-by-browser configuration is compared together by mean per-call wall time, and only a unique global minimum is marked. Total time and call count depend on agent workload and provide context rather than a speed ranking.", - "四种配置中通过数最高": "Highest pass count across four configurations", - "四种配置中通过率最高": "Highest pass rate across four configurations", - "四种配置中单次均值最低": "Lowest mean per-call latency across four configurations", - "速度与浏览器资源": "Speed and browser resources", - "CPU 时间": "CPU time", - "平均 CPU 单核占用": "Mean single-core CPU utilization", - "时间加权平均内存": "Time-weighted mean memory", - "峰值内存": "Peak memory", - "网络接收量": "Network received", - "网络发送量": "Network transmitted", - "仅标记四种配置中数值唯一最低的一项。CPU 时间与网络总量累计得到逻辑结果所用的全部执行;内存来自 Browser Use 执行阶段的浏览器容器采样。": "Only a unique lowest value across the four configurations is marked. CPU time and network totals include every execution used by the logical result; memory comes from browser-container samples during Browser Use execution.", - "四种配置统一比较,只标记唯一最优值;相同值不标。通过数越高越好,耗时和 Tokens 越低越好。": "All four configurations are compared together, and only a unique best value is marked. Ties are unmarked. Higher pass counts are better; lower latency and token use are better.", - "四种完整配置统一比较,每个统计项只标记唯一最优值;相同值不标。这里比较模型 × 浏览器整套配置,不能把差值单独归因于浏览器。": "All four complete configurations are compared together, and only a unique best value is marked for each statistic; ties are unmarked. This compares complete model-by-browser configurations, so a difference cannot be attributed to the browser alone.", - "审查口径:": "Review scope:", - "438/438 次物理执行完成程序完整性核验,75/75 个最终失败完成逐条根因复核;成功结果没有逐条语义盲审,人工复核也没有改写官方 evaluator 分数。": "All 438 physical executions passed programmatic integrity review, and all 75 final failures received case-by-case root-cause review. Successful results did not receive case-by-case blinded semantic review, and human review did not rewrite official evaluator scores.", - "耗时和 Tokens 如何统计": "How latency and tokens are measured", - "Agent 链路累计耗时把取得逻辑结果所发生的执行相加,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。BrowserTool 累计耗时是所有 browser 工具调用的墙钟时间之和,包含工具封装、CDP 往返和页面等待。Tokens 同样累计正式结果包含的物理执行。": "Cumulative Agent-path latency sums the executions used to obtain the logical result. It includes sign-in, model inference, orchestration, page waits, BrowserTool calls, and cleanup, but excludes the preceding Docker site reset. Cumulative BrowserTool latency sums the wall time of all browser-tool calls, including tool-wrapper, CDP round-trip, and page-wait overhead. Tokens likewise sum the physical executions included in the official result.", - "结果分析": "Results analysis", - "完整 90 题结果入口": "Complete 90-task results entry", - "四种配置的通过率、耗时、Tokens 与浏览器资源总表已放在首页,避免在分析页重复。这里直接进入同题矩阵和逐结果失败诊断。": "The home page contains the consolidated pass-rate, latency, token, and browser-resource table for all four configurations. This page proceeds directly to the same-task matrix and per-result failure diagnosis.", - "查看首页核心指标": "View core metrics on the home page", - "四种配置先在同一张表和同一任务矩阵中比较;失败结果可直接打开诊断,查看发生位置、程序报错、复核根因、轨迹、画面和日志。": "The four configurations are compared in one summary table and one same-task matrix. Open any failed result to inspect its location, exact program error, reviewed root cause, trajectory, screenshots, and logs.", - "同题全配置对比": "Same-task comparison across all configurations", - "每行是一道任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is one task, with four model-by-browser configuration columns. Filter for differing outcomes, all-pass tasks, or all-fail tasks.", - "同题全配置矩阵": "Same-task all-configuration matrix", - "每行是同一任务,每列是一个模型×浏览器配置。新增配置只增加列。": "Each row is one task and each column is one model-by-browser configuration. New configurations add columns only.", - "关系": "Outcome pattern", - "结果有差异": "Outcomes differ", - "全部通过": "All pass", - "全部失败": "All fail", - "排除已证实外部干扰后的诊断子集": "Diagnostic subset after removing confirmed external interference", - "官方 90 题结果保持不变。为了看清浏览器和模型差异,下表暂时排除已核验为评测合同、站点、共享环境或参考状态问题的整道任务。": "The official 90-task results remain unchanged. To isolate browser and model differences, this table temporarily removes every task confirmed to have a task-contract, site, shared-environment, or reference-state problem.", - "这是事后诊断口径,不是官方新分数。它按任务统一剔除任一配置已证实受任务/evaluator、站点、共享环境或参考状态干扰的任务,保证四个配置分母一致。": "This is a post-hoc diagnostic view, not a new official score. It removes a whole task across all configurations when any result is confirmed to be affected by task/evaluator, site, shared-environment, or reference-state interference, keeping denominators aligned.", - "失败诊断": "Failure diagnosis", - "发生位置": "Location", - "程序判定": "Program verdict", - "程序报错": "Program error", - "程序没有提供更具体的错误文本。": "The program did not provide a more specific error message.", - "复核结论": "Reviewed conclusion", - "复核结论:": "Reviewed conclusion:", - "负责范围": "Owning scope", - "失败动作": "Failed action", - "验收前最后一步": "Last step before evaluation", - "查看诊断": "Open diagnosis", - "查看期望与实际值": "View expected and actual values", - "查看页面画面": "View page screenshots", - "查看日志与完整证据": "View logs and complete evidence", - "查看完整日志": "View complete logs", - "关闭": "Close", - "尚未完成根因复核。": "Root-cause review is not complete.", - "当前尚无已核验根因。": "No verified root cause is available yet.", - "打开原始 JSON": "Open raw JSON", - "下载原始 JSON": "Download raw JSON", - "在页面中查看完整执行证据": "View complete execution evidence on this page", - "浏览器资源指标": "Browser resource metric", - "浏览器 CPU 时间": "Browser CPU time", - "结束时新增常驻内存": "Additional resident memory at completion", - "浏览器网络记录存在": "Browser network record exists", - "浏览器执行轨迹存在": "Browser execution trace exists", - "Agent 最终回答存在": "Agent final response exists", - "官方判分明细存在": "Official evaluation details exist", - "Agent 事件流存在": "Agent event stream exists", - "环境准备记录存在": "Environment preparation record exists", - "判分明细可读取": "Evaluation details are readable", - "最终回答可读取": "Final response is readable", - "最终回答与运行记录一致": "Final response matches the run record", - "报告分数与官方判分一致": "Reported score matches official evaluation", - "报告状态与官方判分一致": "Reported status matches official evaluation", - "官方 evaluator 版本已记录": "Official evaluator version is recorded", - "官方 evaluator 文件校验一致": "Official evaluator checksum matches", - "官方题集文件校验一致": "Official dataset checksum matches", - "模型请求次数已完整计入": "All model request attempts are accounted for", - "仅保留哈希与字节数(含登录态)": "Hash and byte count only (session-sensitive)", - "已脱敏公开副本": "Sanitized public copy", - "公开原文件": "Public original file", - "按证据清单处理": "Handled according to the evidence inventory", - "WebArena‑Verified 1.2.3 · CowAgent · 模型推理强度固定为低 · 官方程序化 evaluator": "WebArena-Verified 1.2.3 · CowAgent · model reasoning effort fixed to low · official programmatic evaluator", - "失败与干扰项": "Failures and interference", - "在四种配置中统一比较并只标记唯一最优值;相同值不标。通过数越高越好,耗时和 Tokens 越低越好。": "Values are compared across all four configurations, and only a unique best value is marked. Higher pass counts are better; lower latency and token usage are better.", - "查看全部配置的逐题矩阵 →": "View the task-by-task matrix for all configurations →", - "耗时统计包含什么": "What the latency statistics include", - "Agent 总耗时包含模型推理、页面等待和浏览器操作;BrowserTool 耗时是全部浏览器工具调用的墙钟时间之和,仍包含工具封装与 CDP 往返,不是纯内核 CPU 时间。": "Agent latency includes model inference, page waits, and browser operations. BrowserTool latency is the sum of wall-clock time across browser-tool calls; it still includes tool-wrapper and CDP round-trip overhead and is not pure browser-core CPU time.", - "Agent / 模型行为": "Agent / model behavior", - "全部 76 题的速度与浏览器资源": "Performance and browser resources across all 76 tasks", - "均值、P50、P95 都在四种完整配置中统一比较。CPU、内存和网络只计浏览器容器,不含 CowAgent 或模型服务;模型会改变动作数量和页面停留时间,因此这里比较整套配置,不能单独归因于浏览器内核。": "Mean, P50, and P95 are compared across all four complete configurations. CPU, memory, and network measurements cover only the browser container, not CowAgent or the model service. Because the model can change the action count and time spent on pages, these are whole-configuration measurements and cannot be attributed to the browser core alone.", - "每项只标记四种配置中数值唯一最低者。CPU 时间与网络总量累计逻辑结果使用的全部执行;内存来自 Browser Use 执行阶段的浏览器容器采样。": "Only a unique lowest value across the four configurations is marked for each statistic. CPU time and network totals include every execution used by a logical result; memory comes from browser-container samples during Browser Use execution.", - "浏览器证据接口": "Browser evidence interface", - "原因范围": "Cause scope", - "本轮处理": "Treatment in this run", - "修复与验证": "Fix and verification", - "保留": "Retain", - "保留并修复": "Retain and fix", - "排除并修复": "Exclude and fix", - "排除,修复已完成": "Exclude; fix completed", - "排除并重建": "Exclude and rebuild", - "属于当前被测组合;按轨迹定位检索、选择或提交错误。": "Part of the configuration under test; use the trajectory to locate retrieval, selection, or submission errors.", - "修正验收合同,通过独立预检后重跑四种配置。": "Correct the acceptance contract, pass an independent preflight, then rerun all four configurations.", - "修复依赖、会话或保存链路,通过读写预检后重跑。": "Fix the dependency, session, or save path, then rerun after read/write preflight checks.", - "这是浏览器集成能力的一部分;补齐响应正文或 Referer 采集后重跑。": "This is part of browser integration capability; complete response-body or Referer capture and rerun.", - "跨进程串行锁已经上线;受污染任务仍需重跑。": "The cross-process serialization lock is deployed; contaminated tasks still require reruns.", - "重新冻结参考状态,再重跑以区分 Agent 错误。": "Refreeze the reference state, then rerun to distinguish Agent errors.", - "参考状态与 Agent 混合": "Reference state mixed with Agent behavior", - "每个动作同时比较四种配置的单次调用墙钟均值,只标记唯一最低值。总耗时和调用数受 Agent 行为量影响,仅用于解释工作量,不参与快慢判定。": "Each action compares mean per-call wall time across all four configurations and marks only a unique minimum. Total time and call count depend on Agent workload, so they explain workload but are not ranked.", - "默认显示失败。点击“查看诊断”可先在当前页核对发生位置、程序报错、复核根因和处理方案,再进入完整轨迹、画面与日志。": "Failures are shown by default. Select Open diagnosis to inspect the location, exact program error, reviewed root cause, and treatment before opening the full trajectory, screenshots, and logs.", - "该结果通过全部官方程序化验收;下方可核对任务合同、执行轨迹和运行证据。": "This result passed every official programmatic evaluator. The task contract, execution trajectory, and runtime evidence are available below.", - "失败位置": "Failure stage", - "人工复核根因": "Reviewed root cause", - "页面画面": "Page frames", - "查看 evaluator 断言": "View evaluator assertions", - "查看失败附近轨迹": "View trajectory near the failure", - "查看最后页面画面": "View final page frames", - "下载执行日志": "Download execution log", - "核验依据:": "Review evidence:", - "对比口径:": "Comparison treatment:", - "该结果已确认含非模型/浏览器干扰,官方分数保留,但暂不纳入干净子集。": "This result contains confirmed interference outside the model/browser variables. Its official score is retained, but it is temporarily excluded from the clean subset.", - "失败执行的最后页面画面": "Final page frames from the failed execution", - "下图是判分权威执行的最后页面画面,用于核对失败前的可见状态;它们不一定正好对应错误发生的瞬间。": "These are the final page frames from the scoring execution. They help verify visible state before failure but may not capture the exact instant the error occurred.", - "是否属于目标变量": "Target variable?", - "处理": "Treatment", - "运行配置": "Run configuration", - "复核根因": "Reviewed root cause", - "负责对象与处理": "Owner and treatment", - "证据": "Evidence", - "诊断": "Diagnosis", - "画面": "Frames", - "日志": "Logs", - "详情": "Details", - "查看该浏览器的全部结果": "View all results for this browser", - "完整结果只保留一份。下方入口打开结果分析页,并预先筛选当前浏览器;失败诊断、轨迹、页面画面与日志均从该清单进入。": "The report keeps one result inventory. The link below opens it with this browser preselected; failure diagnoses, trajectories, page frames, and logs are all reached from that inventory.", - "打开筛选后的结果清单 →": "Open the filtered result inventory →", - "WebArena-Verified 本地部署、CowAgent 自主操作、双浏览器对照与程序化验收流程": "WebArena-Verified local deployment, autonomous CowAgent operation, browser comparison, and programmatic evaluation" - } + dict(_TRANSLATIONS['TEXT_5']) ) -PHRASES = { - "模板 ID": "template ID", - "自主轨迹示例": "autonomous trajectory example", - "模型推理强度为低": "low reasoning effort", - "步骤预算耗尽": "Step budget exhausted", - "导航语义错误": "Navigation-semantics error", - "检索覆盖不足": "Incomplete retrieval coverage", - "商品选择错误": "Incorrect product selection", - "候选比较错误": "Candidate-comparison error", - "提交字段错误": "Submission-field error", - "检索策略错误": "Retrieval-strategy error", - "流程未完成": "Incomplete workflow", - "结果去重遗漏": "Result-deduplication failure", - "可恢复错误后提前终止": "Premature stop after recoverable error", - "批量操作遗漏": "Incomplete batch workflow", - "提交字段遗漏": "Submission-field omission", - "路由验收缺陷": "Route-acceptance defect", - "evaluator 实现错误": "Evaluator implementation defect", - "任务与验收矛盾": "Instruction-evaluator contradiction", - "表单验收缺陷": "Form-acceptance defect", - "任务状态与验收矛盾": "State-evaluator contradiction", - "站点保存故障": "Site save failure", - "站点依赖故障": "Site dependency failure", - "站点会话故障": "Site session failure", - "网络证据缺失": "Missing network evidence", - "网络证据匹配缺陷": "Network-evidence matching defect", - "响应证据缺失": "Missing response evidence", - "共享环境并发污染": "Shared-environment concurrency contamination", - "检索遗漏与参考状态差异": "Retrieval omission and reference-state drift", - "保留为目标变量;按轨迹修正策略或工具合同": "Keep as a target-variable signal; improve the strategy or tool contract from the trajectory", - "评测集 / evaluator": "Benchmark / evaluator", - "修正合同并重跑;当前仅纳入官方分数": "Fix the contract and rerun; retain only the official score for now", - "修复依赖或保存链路,通过写入预检后重跑": "Fix the dependency or save path, then rerun after a write preflight", - "保留为浏览器集成信号;补齐 HAR 后重跑": "Keep as a browser-integration signal; rerun after completing HAR capture", - "运行环境": "Runtime environment", - "跨进程锁已上线;受污染任务待重跑": "The cross-process lock is implemented; contaminated tasks await rerun", - "任务与 Agent": "Task and Agent", - "先冻结参考状态,再重跑区分 Agent 错误": "Freeze reference state, then rerun to distinguish Agent errors", - "自托管站点": "Self-hosted site", - "浏览器证据接口": "Browser evidence interface", - "保留为对比信号;通过轨迹修正检索、选择和提交策略。": "Keep as a comparison signal; use trajectories to improve retrieval, selection, and submission.", - "任务 / evaluator": "Task / evaluator", - "修正任务合同或 evaluator,验证后重跑;重跑前不进入干净子集。": "Fix the task contract or evaluator, validate it, and rerun; exclude it from the clean subset until then.", - "修复依赖、会话或保存链路,增加任务级写入预检后重跑。": "Fix the dependency, session, or save path and rerun after adding a task-level write preflight.", - "保留为浏览器集成信号;补齐响应正文或 Referer 采集后重跑。": "Keep as a browser-integration signal; rerun after completing response-body or Referer capture.", - "跨进程串行锁已上线;受污染任务待重跑。": "The cross-process serialization lock is implemented; contaminated tasks await rerun.", - "参考状态 + Agent": "Reference state + Agent", - "先重新冻结参考状态,再重跑区分 Agent 错误。": "Refreeze reference state, then rerun to distinguish Agent errors.", - "该执行没有可公开的 trace 页面画面;错误日志与执行证据仍可下载。": "This execution has no publishable trace page frame; the error log and execution evidence remain available.", - "规则 ": "Rule ", - " 秒": " s", - " 次 BrowserTool 调用": " BrowserTool calls", - " 次调用 / ": " calls / ", - " 次传输尝试": " transport attempts", - "总耗时 ": "Total ", - " 次调用": " calls", - "查看 ": "View ", - " 步轨迹": "-step trajectory", - "成功轨迹": "successful trajectory", - "任务 #": "Task #", - "个试次": " attempts", - "个任务": " tasks", - "个失败结果": " failed results", - "实际": "actual", - "期望": "expected", - "环境准备后、Agent 读取前": "After environment preparation and before the Agent read", - "运行结束后的 evaluator 验收": "Evaluator acceptance after execution", - "运行结束后的网络证据验收": "Network-evidence acceptance after execution", - "最终回答/任务状态": "Final answer/task state", - "BrowserTool 执行异常": "BrowserTool execution error", - "步后达到步骤上限": " steps: step limit reached", - "步后的最终回答/任务状态": " steps: final answer/task state", - "步后的站点响应/保存": " steps: site response/save", - "最后页面画面": "Final page frame", - "该逻辑结果包含": "This logical result contains", - "次物理执行。标记为判分权威的执行决定最终得分;累计耗时、tokens 与 BrowserTool 指标是下列各次执行之和。": "physical execution(s). The scoring-authority execution determines the final score; cumulative latency, tokens, and BrowserTool metrics sum all executions shown below.", - "物理执行": "Physical execution", - " · 判分权威": " · scoring authority", - "判分": "Score", - "Agent 链路": "Agent path", - "模型 tokens": "model tokens", - "(完整内容见脱敏事件日志)": "(see the sanitized event log for the complete content)", - "个物理执行均保存环境准备回执;公开环境摘要保留逐项状态,原始登录态不发布。": "physical executions retain environment-preparation receipts. The public environment summary preserves each status while authenticated session state remains private.", - "个物理执行均记录 reset、ready、应用健康检查和登录准备状态;事件流随后证明 CowAgent 自主产生 BrowserTool 调用序列。": "physical executions record reset, readiness, application-health, and sign-in preparation status. The subsequent event streams show CowAgent generating BrowserTool calls autonomously.", - "个逻辑结果及其物理执行、下载路径和公开文件哈希。": "logical results with their physical executions, download paths, and public-file hashes.", - "暂无发布记录。": "No release has been recorded.", -} +PHRASES = dict(_TRANSLATIONS['PHRASES_1']) TEXT.update( - { - "90 题跑完了,但结论不能只看 73、71、70": "Ninety tasks are complete, but 73, 71, and 70 do not tell the whole story", - "90 题、四种配置:相近成绩背后的不同原因": "Ninety tasks and four configurations: different causes behind similar scores", - "四种配置的官方分数很接近;75 个零分经复核后分散在多个责任层,不能统一归给 Agent 或浏览器。": "The four official scores are close. Review placed the 75 zeros across several owning layers rather than assigning them all to the agent or browser.", - "WebArena‑Verified 1.2.3 · CowAgent · reasoning=low · 官方程序化 evaluator": "WebArena-Verified 1.2.3 · CowAgent · reasoning=low · official programmatic evaluator", - "独立阅读版": "Standalone reading edition", - "返回交互报告": "Return to the interactive report", - "论点与证据数据": "Claims and evidence data", - "GPT‑6 Astra 没有在首次运行中输给 GPT‑5.6 Sol": "GPT-6 Astra did not trail GPT-5.6 Sol on the first attempt", - "Astra 首次通过 71/90,Sol 为 70/90": "Astra passed 71/90 on the first attempt; Sol passed 70/90", - "Sol × Chromium 有 3 个重试任务恢复成功,最终到 73/90。Astra 的 71/90 并非首次表现落后。": "Three Sol × Chromium tasks recovered on retry, raising its final score to 73/90. Astra's 71/90 does not reflect weaker first-attempt performance.", - "两层模型共有 9 个单边通过配对": "Nine task pairs were one-sided across the two model cohorts", - "14 题含已确认的外部干扰": "Fourteen tasks contain confirmed external interference", - "干扰来自验收合同、站点、共享环境或参考状态。官方分数保留,另用 76 题事后诊断子集观察目标变量。": "The interference comes from acceptance contracts, sites, shared state, or reference state. Official scores remain unchanged; a post-hoc 76-task subset is used only for diagnosis.", - "Astra 在两个浏览器上都是 71/90;Sol 都是 70/90。Sol × Chromium 有 3 个重试任务恢复成功,最终才到 73/90。": "Astra scored 71/90 with both browsers on the first attempt; Sol scored 70/90. Sol × Chromium reached 73/90 only after three failed tasks recovered on retry.", - "Moli 与 Chromium 的总数接近,不代表执行等价": "Similar totals for Moli and Chromium do not mean equivalent execution", - "Astra 层有 69 题共同通过、2 题各自单边通过;Sol 层是 69 题共同通过、4 题仅 Chromium 通过、1 题仅 Moli 通过。": "Under Astra, both browsers passed 69 tasks and each passed two tasks that the other failed. Under Sol, both passed 69, Chromium alone passed four, and Moli alone passed one.", - "官方 90 题成绩不是干净的模型或内核排名": "The official 90-task score is not a clean model or browser-core ranking", - "14 题已证实受验收合同、站点、共享环境或参考状态干扰。官方分数保留,另用 76 题事后诊断子集观察目标变量。": "Fourteen tasks have confirmed interference from acceptance contracts, sites, shared state, or reference state. Their official scores remain unchanged; a post-hoc 76-task subset is shown only for diagnosis.", - "先把分数读对": "Read the score correctly first", - "首次成功与重试恢复": "First-attempt success and retry recovery", - "报告的主成绩按预先固定的规则取最后一次执行,失败后最多重试一次。因此首次通过率回答“第一次能否完成”,最终通过率还包含“失败后能否恢复”。": "The report's official score uses the last execution under the preregistered rule, with at most one retry after failure. First-attempt success answers whether a task worked immediately; final success also reflects recovery after failure.", - "首次 70/90": "First attempt 70/90", - "首次 71/90": "First attempt 71/90", - "首次通过 70/90": "First-attempt passes: 70/90", - "首次通过 71/90": "First-attempt passes: 71/90", - "失败后重试 20 题": "20 failed tasks retried", - "失败后重试 19 题": "19 failed tasks retried", - "恢复 +3 题": "3 tasks recovered", - "恢复 0 题": "No task recovered", - "首次通过": "First-attempt passes", - "重试任务": "Retried tasks", - "重试恢复": "Recovered on retry", - "官方最终通过": "Official final passes", - "为什么看起来 Astra 更低?": "Why does Astra appear lower?", - "只有在 Chromium 的最终分数上,Astra 的 71 低于 Sol 的 73。这 2 题差距不是首次能力差:Sol 首次只有 70,随后恢复 3 题;Astra 首次 71,没有重试恢复。Astra 的 Task 77 还受共享环境污染。": "Astra trails Sol only in the final Chromium score, 71 versus 73. That two-task gap did not exist on the first attempt: Sol started at 70 and recovered three tasks, while Astra started at 71 and recovered none. Astra's Task 77 was also contaminated by shared-state concurrency.", - "两个模型层在不同日期、不同 browser‑eval 提交上运行(GPT-5.6 Sol 51a37526、GPT-6 Astra bed8469e)。任务、CowAgent 提交、浏览器镜像与 evaluator 数据相同,但这仍不是严格的模型 A/B。": "The model cohorts ran on different dates and browser-eval commits (GPT-5.6 Sol 51a37526; GPT-6 Astra bed8469e). They share tasks, CowAgent commit, browser images, and evaluator data, but this is still not a strict model A/B test.", - "总数相近,不是同一批任务相近": "Similar totals can hide different task outcomes", - "按同题比较浏览器": "Compare browsers task by task", - "浏览器对比必须留在同一模型层内。汇总数会隐藏互换的成败任务;下表给出同题对照。": "Browser comparisons must stay within a model cohort. Aggregate totals hide swapped outcomes; the table gives the paired view.", - "浏览器对比必须留在同一模型层内。它们各自的总数会隐藏互换的成败任务;下表才是同题对照。": "Browser comparisons must stay within a model cohort. Aggregate totals hide tasks that swap outcomes; the paired table shows the relevant comparison.", - "每行只比较同一模型下的 90 个配对任务。": "Each row compares the 90 paired tasks within one model cohort.", - "两个模型层合计有 9 个单边通过,可用于定位兼容性问题;样本量和方向都不支持总体优劣判断。": "Across both model cohorts, nine task pairs were one-sided. They help locate compatibility defects, while the sample size and direction do not support an overall ranking.", - "查看 90 题的四配置矩阵 →": "View the four-configuration matrix for all 90 tasks →", - "错误到底发生在哪里": "Where the failures occurred", - "程序判分落点与复核根因": "Programmatic failure location and reviewed root cause", - "程序判分先说明哪类规则未通过;人工复核再沿最终回答、evaluator 断言、HAR 和 Agent 轨迹追到直接原因。这两个层次需要分开。": "Programmatic scoring first identifies which rule class failed. Human review then follows the final answer, evaluator assertions, HAR, and agent trajectory to the direct cause. These are separate layers.", - "程序判分落点": "Programmatic failure location", - "只有网络副作用规则未通过": "Only the network-side-effect rule failed", - "evaluator 没找到合同要求的请求或可观察副作用": "The evaluator did not find the required request or observable side effect", - "只有最终回答规则未通过": "Only the final-answer rule failed", - "最终回答与结构化答案合同不一致": "The final answer disagreed with the structured answer contract", - "两类规则同时未通过": "Both rule classes failed", - "最终回答和网络副作用均未满足合同": "Neither the final answer nor the network side effect satisfied the contract", - "官方 evaluator 的失败规则组合": "Failed rule combinations from the official evaluator", - "“网络副作用未通过”只描述程序判分位置。它可能源于 Agent 没有提交,也可能源于 evaluator、站点或 HAR 采集问题。": "A failed network-side-effect rule identifies the scoring location only. It can result from an agent not submitting, or from the evaluator, site, or HAR capture.", - "复核根因": "Reviewed root cause", - "75 个失败结果的复核原因分布": "Reviewed cause distribution for 75 failed results", - "75 个官方失败结果都读取了最终回答、evaluator 断言、HAR 和 Agent 轨迹。分类统计不是第二套打分,而是回答“这个 0 分由哪一层直接产生”。": "All 75 official failures were reviewed against the final answer, evaluator assertions, HAR, and agent trajectory. The categories do not rescore tasks; they identify the layer that directly produced each zero.", - "检索不全、选错对象、字段遗漏或流程提前结束": "Incomplete retrieval, wrong selection, omitted fields, or premature termination", - "验收合同与页面语义不一致,或 evaluator 自身报错": "Acceptance contracts disagree with page semantics, or the evaluator itself errors", - "搜索、会话或保存依赖故障": "Search, session, or persistence dependency failure", - "HAR 缺少 Referer、响应正文或出现重复记录": "HAR lacks Referer or response bodies, or contains duplicate records", - "共享环境": "Shared environment", - "并发执行改写了任务初始状态": "Concurrent execution changed the task's initial state", - "参考漂移与 Agent 遗漏同时存在": "Reference drift and an agent omission both contributed", - "Agent / 模型行为": "Agent / model behavior", - "保留并修复": "Retain and fix", - "Agent 行为": "Agent behavior", - "例如漏掉商品、选错最优候选、写错字段,或在 40 步前提前结束。这些属于当前 Agent × 模型配置的真实失败。": "Examples include missing products, choosing the wrong candidate, writing the wrong field, or stopping before the 40-step limit. These are genuine failures of the current agent-model configuration.", - "验收规则": "Acceptance rules", - "Task 699 的 evaluator 在比较实际提交前就因单元素 Alternatives 配置抛错;Task 528 要求“不提交”,验收却要求 POST。这些 0 分不能归给模型或浏览器。": "Task 699's evaluator throws on a one-item Alternatives configuration before comparing the submission. Task 528 says not to submit, while its evaluator requires a POST. These zeros cannot be attributed to the model or browser.", - "站点与环境": "Sites and environment", - "Magento 搜索或库存保存服务失效,以及 Task 77 的并发状态污染,都会让正确操作无法通过。": "Magento search or stock-save failures, plus shared-state contamination in Task 77, can prevent otherwise correct work from passing.", - "浏览器证据": "Browser evidence", - "的 evaluator 在比较实际提交前就因单元素 Alternatives 配置抛错;": " fails before comparing the actual submission because the evaluator throws on a one-item Alternatives configuration; ", - "要求“不提交”,验收却要求 POST。这些 0 分不能归给模型或浏览器。": " says not to submit, while its evaluator requires a POST. These zeros cannot be attributed to the model or browser.", - "Magento 搜索或库存保存服务失效,以及": "Magento search or stock-save services failed, and ", - "的并发状态污染,都会让正确操作无法通过。": " suffered shared-state contamination. Either can prevent correct work from passing.", - "Moli 的部分 HAR 没有 Referer 或响应正文,例如": "Some Moli HAR records omit Referer or response bodies, including ", - "。页面可能已经操作成功,但 evaluator 无法从网络记录确认;这是浏览器集成层的有效信号。": ". The UI operation may have succeeded, but the evaluator cannot confirm it from network evidence. This is a valid browser-integration signal.", - "Moli 的部分 HAR 没有 Referer 或响应正文。页面可能已经操作成功,但 evaluator 无法从网络记录确认;这是浏览器集成层的有效信号。": "Some Moli HAR records omit Referer or response bodies. The UI operation may have succeeded, but the evaluator cannot confirm it from network evidence. This is a valid browser-integration signal.", - "官方成绩与诊断子集分开看": "Keep official scores separate from the diagnostic subset", - "历史官方分数不人工改写。对已证实受任务 / evaluator、站点、共享环境或参考状态干扰的 14 题,诊断时按整题排除四种配置,剩余 76 题。": "Historical official scores are never manually rewritten. For diagnosis, 14 tasks with confirmed task/evaluator, site, shared-state, or reference-state interference are removed across all four configurations, leaving 76 tasks.", - "76 题通过": "Passes on 76 tasks", - "诊断通过率": "Diagnostic success rate", - "这是事后诊断口径,用于确定修复顺序,不是新的官方分数。只有修复后重跑完整四配置,才能产生新成绩。": "This post-hoc view prioritizes fixes; it is not a replacement official score. A new score requires rerunning all four configurations after the fixes.", - "这份结果审查到了什么": "What the review established", - "审查覆盖与边界": "Review coverage and boundaries", - "438/438 次物理执行检查了 HAR、Agent trace、最终回答、evaluator 输出和环境准备回执。": "All 438 physical executions were checked for HAR, agent trace, final answer, evaluator output, and environment-preparation receipts.", - "438/438 个证据包由程序核对文件存在性、哈希、JSON 可读性、回答 / 分数 / 状态一致性和运行身份。": "Programs checked all 438 evidence bundles for file presence, hashes, JSON readability, response/score/status consistency, and runtime identity.", - "官方程序验收": "Official programmatic evaluation", - "每题按最终回答和必要网络副作用判分;没有 LLM judge。": "Each task is scored from its final answer and required network side effects; no LLM judge is used.", - "失败根因复核": "Failure root-cause review", - "75/75 个最终失败结果逐条查看错误、断言、轨迹与网络记录,归类并给出处理边界。": "All 75 final failures were reviewed against errors, assertions, trajectories, and network records, then classified with a defined treatment boundary.", - "75/75 个最终失败结果逐条阅读错误、断言、最终回答、HAR 和 Agent 轨迹,归类并给出处理边界。": "All 75 final failures received case-by-case review of errors, assertions, final answers, HAR, and agent trajectories, followed by classification and treatment boundaries.", - "审查分为完整性检查和失败根因复核。": "The review has two layers: integrity checks and failure root-cause review.", - "前者确认“证据包与 evaluator 记录一致”;后者检查 evaluator 语义,并识别出 Task 699 等问题。已通过结果没有再做独立语义盲审,其判定依据仍是官方 evaluator。": "The first confirms that evidence bundles agree with evaluator records. The second checks evaluator semantics and identified issues such as Task 699. Passed results did not receive a separate blinded semantic review and still rely on the official evaluator.", - "查看核验记录 →": "View review records →", - "性能与资源:只在四种配置首次都成功的任务上看": "Performance and resources on tasks all four configurations passed on the first attempt", - "共同成功任务的性能与资源": "Performance and resources on common final successes", - "当前没有四种配置最终都成功的任务,资源差异暂不比较。": "No task was finally passed by all four configurations, so resource differences are unavailable.", - "首次共同成功任务的资源差异": "Resource differences on common first-attempt successes", - "当前没有四种配置首次都成功的任务,资源差异暂不比较。": "No task was passed on the first attempt by all four configurations, so resource differences are unavailable.", - "Agent 链路还包含模型推理和页面等待,因此不能解读为纯内核性能。": "The agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", - "查看口径与四配置明细 →": "View the cohort definition and four-configuration details →", - "Moli 的内存区间更低": "Moli has the lower memory range", - "Chromium 的工具耗时与 CPU 更低": "Chromium has lower tool latency and CPU time", - "下表限定为首次执行共同成功的 64 题,每个配置只取第一次执行并报告 P50。这既排除了重试累计,也避免把“很快失败”错当成速度优势。": "The table uses the 64 tasks that all four configurations passed on the first attempt. It reports P50 from the first execution only, excluding retry accumulation and the false speed advantage of fast failures.", - "峰值内存 P50": "Peak memory P50", - "CPU 时间 P50": "CPU time P50", - "Moli 的优势很明确:内存": "Moli's clear advantage is memory", - "Moli:峰值内存更低": "Moli: lower peak memory", - "这批首次共同成功任务上,Moli 的峰值内存 P50 约为 190 MiB,Chromium 约为 500–567 MiB。这个差距在两个模型层都出现。": "On these common first-attempt successes, Moli's P50 peak memory is about 190 MiB versus roughly 500-567 MiB for Chromium. The gap appears in both model cohorts.", - "代价也很明确:工具耗时和 CPU": "The tradeoff is tool latency and CPU time", - "Chromium:工具耗时与 CPU 更低": "Chromium: lower tool latency and CPU time", - "同一批任务上,Chromium 的 BrowserTool P50 和 CPU 时间均低于 Moli。整条 Agent 链路还包含模型推理和页面等待,不能解读为纯内核性能。": "On the same tasks, Chromium has lower BrowserTool P50 and CPU time than Moli. The full agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", - "同一批任务上,Moli 的 BrowserTool P50 和 CPU 时间都高于 Chromium。整条 Agent 链路还包含模型推理和页面等待,不能解读为纯内核性能。": "On the same tasks, Moli has higher BrowserTool P50 and CPU time than Chromium. The full agent path also includes model inference and page waits, so it is not a pure browser-core measurement.", - "最后怎么读这份报告": "The practical reading", - "工程结论": "Engineering conclusions", - "官方 90 题成绩显示,四种端到端配置都完成了大约八成任务,浏览器之间的单边差异集中在少数任务。75 个零分分布在 Agent、验收规则、站点、浏览器证据接口和共享环境等多层。": "The official 90-task scores show that all four end-to-end configurations complete about four-fifths of the sample, with one-sided browser differences concentrated in a few tasks. The 75 zeros span agent behavior, acceptance rules, sites, browser-evidence interfaces, and shared state.", - "官方 90 题成绩说明,四种端到端配置都完成了大约八成任务,浏览器之间的单边差异集中在少数任务。错误复核又证明,不能把 75 个零分全部归给 Agent 或浏览器。": "The official 90-task scores show that all four end-to-end configurations complete about four-fifths of the sample and that one-sided browser differences concentrate in a few tasks. Failure review also shows that the 75 zeros do not all belong to the agent or browser.", - "对工程选型有用的结论是:Moli 的内存占用明显更低,但当前 HAR 完整性、工具耗时和部分任务兼容性还需修复;Chromium 在 76 题诊断子集中两个模型层都多通过任务,但当前只有一个正式样本,不能外推成总体优势。": "For engineering decisions, Moli uses substantially less memory, while HAR completeness, tool latency, and some task compatibility still need work. Chromium passes more tasks in both model cohorts on the 76-task diagnostic subset, but one formal sample cannot establish a general advantage.", - "下一轮只做两件事": "Two priorities for the next run", - "下一轮验证": "Next validation run", - "Task 77 与 771 可在串行锁下补跑;Task 339、475、600、625、676、699 的合同或 evaluator 修复已提交": "Tasks 77 and 771 can be rerun under the serialization lock. Contract or evaluator fixes for tasks 339, 475, 600, 625, 676, and 699 have been submitted in", - "上游 PR #45": "upstream PR #45", - ",合并后补跑;Task 528 在具备可移植的未提交表单验收前继续排除。站点题先通过读写预检。任何补跑都覆盖同题四种配置,并作为新的完整证据批次发布。": ". They will be rerun after the fix is merged. Task 528 remains excluded until a portable evaluator can verify an unsubmitted filled form. Site-dependent tasks must first pass read/write preflight checks. Every rerun covers all four configurations for the task and is published as a new complete evidence cohort.", - "先修复并重跑 14 题干扰项,再对同一冻结代码的四种配置增加重复次数。在此之前,这份报告用于定位兼容性和证据链问题,不发布模型或浏览器排名。": "First fix and rerun the 14 interference tasks; then add repetitions for all four configurations on one frozen codebase. Until then, this report is for locating compatibility and evidence-chain defects, not for publishing a model or browser ranking.", - "先修复并重跑 14 题干扰项,再对同一冻结代码的四种配置增加重复次数。在此之前,这份报告适合找兼容性和证据链问题,不适合发布模型或浏览器排名。": "First fix and rerun the 14 interference tasks; then add repetitions for all four configurations on one frozen codebase. Until then, this report is useful for locating compatibility and evidence-chain defects, not for publishing a model or browser ranking.", - "查看全部配置的逐题矩阵 →": "View the per-task matrix for all configurations →", - "耗时统计包含什么": "What the latency metrics include", - "Agent 总耗时包含模型推理、页面等待和浏览器操作;BrowserTool 耗时是全部浏览器工具调用的墙钟时间之和,仍包含工具封装与 CDP 往返,不是纯内核 CPU 时间。": "Agent total time includes model inference, page waits, and browser actions. BrowserTool time is the summed wall time of all browser-tool calls; it still includes tool-wrapper and CDP round-trip overhead and is not pure browser-core CPU time.", - "把取得逻辑结果所发生的执行相加,包含登录、模型推理、调度、页面等待、BrowserTool 与收尾,不包含此前的 Docker 站点重置。": " sums the executions used to obtain the logical result, including sign-in, model inference, orchestration, page waits, BrowserTool calls, and cleanup, but excluding the preceding Docker site reset. ", - "是所有 browser 工具调用的墙钟时间之和,包含工具封装、CDP 往返和页面等待。Tokens 同样累计正式结果包含的物理执行。": " is the sum of wall time across all browser-tool calls, including tool-wrapper, CDP round-trip, and page-wait overhead. Tokens likewise sum the physical executions included in the official result.", - "Browser Use 能力评测": "Browser Use capability evaluation", - "首次结果与重试恢复分开统计": "Separate first-attempt results from retry recovery", - "最终通过": "Final passes", - "查看评测集": "View dataset", - "下钻全部结果": "Inspect all results", - "下载论点与证据": "Download claims and evidence", - "Task 77 的并发污染已经由串行锁修复,但旧结果不会人工改分;合同或站点尚不能可靠验收的题也不进入分母。": "A serialization lock fixes the Task 77 concurrency defect, but the old result is not manually rescored. Tasks whose contracts or sites still cannot be evaluated reliably are also excluded from the denominator.", - "正式比较与原始记录分开看": "Separate the formal comparison from raw records", - "Task 77 可在串行锁下补跑;站点题先通过读写预检,合同矛盾题由上游修正或退出样本。任何补跑都覆盖同题四种配置,并作为新的完整证据批次发布。": "Task 77 can be rerun under the serialization lock. Site-dependent tasks must first pass read/write preflight checks, while contradictory contracts must be corrected upstream or excluded. Every rerun covers all four configurations for the task and is published as a new complete evidence cohort.", - "每行是一道审查合格任务,四列分别对应模型×浏览器配置;可筛出结果有差异、全部通过或全部失败的任务。": "Each row is a review-qualified task, with one column per model-browser configuration. Filters isolate divergent, all-pass, or all-fail outcomes.", - "这里保留预注册 90 题的全部原始结果,包括退出正式比较的 14 题。默认显示失败;点击“查看诊断”可核对发生位置、程序报错、复核根因、轨迹、画面和日志。": "This inventory retains raw results for all 90 preregistered tasks, including the 14 excluded from the formal comparison. Failures are shown by default; open a diagnosis to inspect the failure point, program error, reviewed cause, trajectory, frames, and logs.", - } + dict(_TRANSLATIONS['TEXT_6']) ) @@ -2326,8 +747,15 @@ def _header(source: PurePosixPath, english: PurePosixPath) -> str: ) +def bilingual_text(zh: str, en: str) -> str: + """Keep dynamic interface text and its English form beside the same values.""" + return f'{html.escape(zh)}' + + def localize_html(page: str, source: PurePosixPath) -> str: english = PurePosixPath("en") / source + page = re.sub(r'[^<]*', + lambda match: html.escape(html.unescape(match[1])), page) page = page.replace('', '', 1) page = re.sub( r"(.*?) · Browser Use 评测", diff --git a/harness/browser_use/report_package.py b/harness/browser_use/report_package.py index 52aa5bfe9..73ba9a644 100644 --- a/harness/browser_use/report_package.py +++ b/harness/browser_use/report_package.py @@ -5,16 +5,19 @@ import argparse import hashlib +import json from pathlib import Path +from harness.browser_use.failure_cause_review import review_path from harness.browser_use.report_check import check from harness.browser_use.report_visual import check as check_visual from harness.browser_use.report_visual import receipt_is_current +from harness.browser_use.selection import report_selection_path +from harness.reporting.assets import bound_json_asset from harness.reporting.links import check as check_links from harness.reporting.publication import package_archive ATTESTATION = Path("publish/browser-use-sites.attestation.json") -PROFILE = Path("evaluation/browser-use/current-report.json") VISUAL_RECEIPT = Path("publish/browser-use-visual-qa.json") @@ -38,11 +41,12 @@ def tree_sha256(root: Path) -> str: def validate_release( report: Path, evidence: list[Path], visual_receipt: Path, - *, authority_root: Path | None = None, + *, authority_root: Path | None = None, profile_path: Path | None = None, ) -> None: """Run every release gate before creating a publishable archive.""" - check(report, evidence, None, require_complete=True, authority_root=authority_root) + check(report, evidence, None, require_complete=True, + authority_root=authority_root, profile_path=profile_path) check_links(report) if not receipt_is_current(report, visual_receipt): check_visual(report, visual_receipt) @@ -51,11 +55,25 @@ def validate_release( def package( report: Path, evidence: list[Path], output: Path, visual_receipt: Path, *, authority_root: Path | None = None, attestation_path: Path = ATTESTATION, + profile_path: Path | None = None, ) -> dict: authority = authority_root if authority_root is not None else Path(__file__).parents[2] - profile = authority / PROFILE + if profile_path is None and authority_root is None: + raise ValueError("An explicit report profile is required; pass --profile") + profile = profile_path or authority / "evaluation/browser-use/current-report.json" names = ("manifest.json", "run-config.json", "attempts.jsonl", "result-review.json") - inputs = [authority / "evaluation", authority / "research"] + inputs = [profile] + profile_data = json.loads(profile.read_text()) + history_path, _ = bound_json_asset(profile_data.get("report_history"), root=profile.parent) + if history_path is not None: + inputs.append(history_path) + inputs.append(report_selection_path(profile_data, authority_root=profile.parent)) + review = review_path(profile_data, authority_root=profile.parent) + if review.is_file(): + inputs.append(review) + frozen = profile_data.get("official_dataset") + if frozen: + inputs.append(Path(frozen["path"])) inputs.extend(path / name for path in evidence for name in names) def attest(archive: Path) -> dict: @@ -73,7 +91,7 @@ def attest(archive: Path) -> dict: return package_archive( report, output, attestation_path, inputs=inputs, validate=lambda: validate_release(report, evidence, visual_receipt, - authority_root=authority), + authority_root=authority, profile_path=profile_path), attest=attest, excluded=(".generated-report",), ) @@ -86,10 +104,14 @@ def main() -> None: parser.add_argument("--output", type=Path, default=Path("publish/browser-use-sites.zip")) parser.add_argument("--visual-receipt", type=Path, default=VISUAL_RECEIPT) parser.add_argument("--authority-root", type=Path) + parser.add_argument("--profile", type=Path, required=True) parser.add_argument("--attestation", type=Path, default=ATTESTATION) args = parser.parse_args() - package(args.report, args.evidence, args.output, args.visual_receipt, - authority_root=args.authority_root, attestation_path=args.attestation) + package( + args.report, args.evidence, args.output, args.visual_receipt, + authority_root=args.authority_root, attestation_path=args.attestation, + profile_path=args.profile, + ) print(args.output) diff --git a/harness/browser_use/report_publish.py b/harness/browser_use/report_publish.py index 51f0fce81..8eb7dc96a 100644 --- a/harness/browser_use/report_publish.py +++ b/harness/browser_use/report_publish.py @@ -31,7 +31,7 @@ ARCHIVE = Path("publish/browser-use-sites.zip") ATTESTATION = Path("publish/browser-use-sites.attestation.json") RECEIPT = Path("publish/browser-use-artifact-hub-receipt.json") -PROFILE = Path("evaluation/browser-use/current-report.json") +PROFILE: Path | None = None OPERATION_JOURNAL = Path("publish/browser-use-artifact-site-operation-pending.json") BINDING_JOURNAL = Path("publish/browser-use-artifact-site-binding-pending.json") PUBLIC_REPORT_MARKER = "首页 · Browser Use 评测" @@ -402,7 +402,7 @@ def cli() -> None: ) parser.add_argument("--archive", type=Path, default=ARCHIVE) parser.add_argument("--attestation", type=Path, default=ATTESTATION) - parser.add_argument("--profile", type=Path, default=PROFILE) + parser.add_argument("--profile", type=Path, required=True) parser.add_argument("--receipt", type=Path, default=RECEIPT) parser.add_argument("--binding-journal", type=Path, default=BINDING_JOURNAL) parser.add_argument("--operation-journal", type=Path, default=OPERATION_JOURNAL) diff --git a/harness/browser_use/selection.py b/harness/browser_use/selection.py index b1e90004f..b98a225cf 100644 --- a/harness/browser_use/selection.py +++ b/harness/browser_use/selection.py @@ -223,10 +223,15 @@ def load_pilot(path: str | Path) -> PilotSelection: if len(ids) != len(set(ids)): raise ValueError("pilot task IDs must be unique") counts = {tier: sum(task.tier == tier for task in tasks) for tier in TIER_COUNTS} - if counts != TIER_COUNTS: - raise ValueError(f"pilot tier counts must be {TIER_COUNTS}, got {counts}") - if metadata.get("tier_counts") != TIER_COUNTS: - raise ValueError("selection metadata tier_counts must match the frozen tier counts") + declared = metadata.get("tier_counts") + if (not isinstance(declared, dict) or set(declared) != set(TIER_COUNTS) + or any(type(value) is not int or value < 0 for value in declared.values()) + or sum(declared.values()) == 0): + raise ValueError("selection metadata must declare nonnegative tier counts") + if counts != declared: + raise ValueError( + f"pilot tier counts must match the frozen declaration {declared}, got {counts}" + ) for task in tasks: low, high = TIER_BOUNDS[task.tier] if task.estimated_min_steps < low or (high is not None and task.estimated_min_steps > high): @@ -320,3 +325,24 @@ def validate_against_official( f"{expected_task_type_counts}, got {dict(task_type_counts)}" ) return resolved + + +def report_selection_path( + profile: dict, explicit: Path | None = None, *, authority_root: Path | None = None +) -> Path: + """Use the same frozen selection in report construction and packaging.""" + from lexbench_webfetch.paths import contained_path + + from harness.web_fetch.safety import unlinked_path + binding = profile.get("task_selection") or {} + value = explicit or binding.get("path") or "evaluation/browser-use/webarena-verified-pilot.json" + path = Path(value) + root = authority_root or Path(__file__).parents[2] + path = (unlinked_path(path) if path.is_absolute() else + contained_path(root, str(path), reject_symlinks=True)) + if binding and ( + _sha256(path) != binding.get("sha256") + or binding["sha256"] != profile.get("selection_sha256") + ): + raise ValueError("report selection differs from its instance profile") + return path diff --git a/harness/lexbench/assets/render_contract.cjs b/harness/lexbench/assets/render_contract.cjs index adf887e42..f0cb00aef 100644 --- a/harness/lexbench/assets/render_contract.cjs +++ b/harness/lexbench/assets/render_contract.cjs @@ -21,7 +21,9 @@ function render(model, lang, expression) { let context = { document: { documentElement: { dataset: { base: "" }, lang }, - getElementById: () => null, + getElementById: (id) => id === "report-messages" ? { textContent: site + ? fs.readFileSync(path.join(site, "index.html"), "utf8").match(/id="report-messages">([\s\S]*?)<\/script>/)[1] + : fs.readFileSync(path.join(path.dirname(app), "messages.json"), "utf8") } : null, }, location: { hash: "" }, MODEL: model, @@ -104,6 +106,10 @@ const model = { ], }; for (let lang of languages) { + const comparison = render(model, lang, "fixedComparisonSection()"); + assert.ok(comparison.includes(lang === "zh" ? "结果关系" : "Outcome relation")); + assert.ok(!comparison.includes("undefined")); + assert.throws(() => render(model, lang, 'message("summary_chart")'), /Missing report message parameter/); const html = render(model, lang, "candidateResourceSection()"); resourceHighlights(html); assert.throws(() => resourceHighlights(html.replace("num winner", "num"))); @@ -287,6 +293,10 @@ if (site) { fs.readFileSync(path.join(site, "data/model.json"), "utf8"), ); for (const lang of languages) { + const changedInputs = render({...measured, input_definition_variants: {first: {}, second: {}}}, lang, "inputVersionNote()"); + assert.ok(changedInputs.includes('data-input-definition-variants="2"')); + assert.ok(changedInputs.includes(String(measured.task_count - 2))); + assert.equal(render({...measured, input_definition_variants: {}}, lang, "inputVersionNote()"), ""); for (const [off, on, official, layoutNet, versionNet] of [ [4, 2, 3, -2, -1], [1, 4, 2, 3, 2], [2, 2, 2, 0, 0], ]) { @@ -347,7 +357,7 @@ if (site) { for (let page of ["home()", "guidePage()"]) { const html = render(measured, lang, page); resourceHighlights(html); - assert.ok(html.includes("moli-all-success-resources-")); + assert.ok(html.includes("comparison-all-success-resources-")); assert.equal((html.match(/data-combined-chart/g) || []).length, 1); assert.equal( (html.match(/data-functional-method=/g) || []).length, diff --git a/harness/lexbench/report.py b/harness/lexbench/report.py index 7eac6a482..c43198eaf 100644 --- a/harness/lexbench/report.py +++ b/harness/lexbench/report.py @@ -12,9 +12,6 @@ import statistics ROOT = pathlib.Path(os.environ.get("BROWSER_EVAL_DATA_HOME", pathlib.Path.home() / ".browser-eval")) -BENCH_ROOT = pathlib.Path( - os.environ.get("LEXBENCH_REPORT_RUNNER_ROOT", ROOT / "work/moli-1.1.8-failures-linux") -) from harness.record_io import load_jsonl from harness.report_languages import english_notice, generated_languages, write_language_contract from harness.resource_calibration import duration_calibration @@ -26,12 +23,18 @@ from .resource_summary import summarize as summarize_linux_profiles from .resource_summary import validate_summary as validate_linux_summary -DEFAULT_RUNS = ROOT / "runs/moli-1.1.8-failures-linux" REPOSITORY_ROOT = pathlib.Path(__file__).resolve().parents[2] -DEFAULT_SOURCE = REPOSITORY_ROOT / "scripts/moli_report" -DEFAULT_OUTPUT = ROOT / "reports/moli-1.1.8-failures-site" -VERSION_RUNS = ROOT / "runs/moli-114-118-memory-repro" -DEFAULT_FUNCTIONAL_CORRECTION = ROOT / "runs/moli-method-corrections-20260920" + + +def read_comparison_task(roots, relative, *, expected_sha256): + """Read exact measured task bytes from the declared source roots.""" + for root in roots: + path = contained_path(root, relative, reject_symlinks=True) + if path.is_file(): + data = path.read_bytes() + if hashlib.sha256(data).hexdigest() == expected_sha256: + return data + raise ValueError("Measured task bytes are missing from declared sources: " + str(relative)) def write_presentation(source, output, combined, version, macos=None, *, include_english=False): @@ -49,7 +52,7 @@ def write_presentation(source, output, combined, version, macos=None, *, include for name, comparison, metrics in comparisons: for language in languages: render_chart( - comparison, assets / f"moli-{name}-success-resources-{language}.svg", + comparison, assets / f"comparison-{name}-success-resources-{language}.svg", metrics, language, ) entries = [("zh-CN", "", "index.html")] @@ -62,7 +65,8 @@ def write_presentation(source, output, combined, version, macos=None, *, include for language, base, entry in entries: target = output / entry target.parent.mkdir(exist_ok=True) - shell = template.replace("{{lang}}", language).replace("{{base}}", base) + messages = (source / "messages.json").read_text().replace("<", "\\u003c") + shell = template.replace("{{lang}}", language).replace("{{base}}", base).replace("{{messages}}", messages) for name in ("style.css", "app.js"): digest = hashlib.sha256((source / name).read_bytes()).hexdigest()[:12] shell = shell.replace(name, f"{name}?v={digest}") @@ -74,20 +78,20 @@ def main(argv=None): parser.add_argument( "--profile", type=pathlib.Path, - default=REPOSITORY_ROOT / "evaluation/moli/current-report.json", + required=True, ) - parser.add_argument("--cohort", type=pathlib.Path, default=DEFAULT_RUNS / "report-cohort.json") + parser.add_argument("--cohort", type=pathlib.Path) parser.add_argument("--summary", type=pathlib.Path) - parser.add_argument("--source", type=pathlib.Path, default=DEFAULT_SOURCE) - parser.add_argument("--output", type=pathlib.Path, default=DEFAULT_OUTPUT) + parser.add_argument("--source", type=pathlib.Path) + parser.add_argument("--output", type=pathlib.Path) parser.add_argument( "--include-english", action="store_true", help="Also generate English pages and charts" ) parser.add_argument( - "--vendor-dir", type=pathlib.Path, default=ROOT / "reports/moli-1.1.8-failures-src" + "--vendor-dir", type=pathlib.Path ) parser.add_argument( - "--functional-correction", type=pathlib.Path, default=DEFAULT_FUNCTIONAL_CORRECTION + "--functional-correction", type=pathlib.Path ) parser.add_argument( "--fixed-summary", @@ -101,10 +105,25 @@ def main(argv=None): parser.add_argument("--resource-matrix", type=pathlib.Path) parser.add_argument("--linux-resource-profiles", type=pathlib.Path) args = parser.parse_args(argv) + report_profile = json.loads(args.profile.read_bytes()) + paths = report_profile.get("paths", {}) + + def configured_path(name, override=None): + value = override if override is not None else paths.get(name) + if not value: + parser.error(f"Report profile requires paths.{name} or an explicit CLI path") + value = pathlib.Path(value).expanduser() + return value if value.is_absolute() else (REPOSITORY_ROOT if name == "source" else ROOT) / value + + for name in ("cohort", "source", "output", "vendor_dir", "functional_correction"): + setattr(args, name, configured_path(name, getattr(args, name))) + BENCH_ROOT = configured_path("runner", os.environ.get("LEXBENCH_REPORT_RUNNER_ROOT")) + VERSION_RUNS = configured_path("version_runs") + VERSION_CANDIDATE = configured_path("version_candidate") + LAYOUT_POLICY_ROOT = configured_path("layout_policy_runner") RUNS = args.cohort.parent if args.summary is None: args.summary = RUNS / "report-runs.json" - report_profile = json.loads(args.profile.read_bytes()) resource_builds = report_profile.get("resource_builds", {}) # The report profile names the experiment; the chart schema names plotted calls. # Convert once at this boundary instead of passing the profile to render_chart. @@ -771,7 +790,7 @@ def timestamp(value): "Invalid Lexbench report evidence: set(version_protocol['functional_tasks']) == set(selected) and len(version_protocol['functional_tasks']) == len(selected)" ) functional_114 = contained_path( - VERSION_RUNS, "114_off_differential_candidate_20260920", reject_symlinks=True + VERSION_CANDIDATE.parent, VERSION_CANDIDATE.name, reject_symlinks=True ) functional_manifest = json.loads( contained_path(functional_114, "run_manifest.json", reject_symlinks=True).read_text() @@ -1023,7 +1042,7 @@ def timestamp(value): "Invalid Lexbench report evidence: re.fullmatch('[A-Za-z0-9_-]+', run_id)" ) run_root = contained_path( - ROOT / "work/lexbench-layout-policy-v3", + LAYOUT_POLICY_ROOT, pathlib.Path("runs") / run_id, reject_symlinks=True, ) @@ -1057,20 +1076,12 @@ def timestamp(value): source_tasks[task["task_id"]] = task for case in candidate["cases"]: task = source_tasks[case["task_id"]] - bench_root = ROOT / "work/lexbench-layout-policy-v3" - task_path = contained_path(bench_root, task["path"], reject_symlinks=True) - if not ( - task_path.resolve().is_relative_to(bench_root.resolve()) - and (not task_path.is_symlink()) - ): - raise ValueError( - "Invalid Lexbench report evidence: task_path.resolve().is_relative_to(bench_root.resolve()) and (not task_path.is_symlink())" - ) - task_bytes = task_path.read_bytes() - if not sha(task_bytes) == task["sha256"] == case["task_sha256"]: - raise ValueError( - "Invalid Lexbench report evidence: sha(task_bytes) == task['sha256'] == case['task_sha256']" - ) + if task["sha256"] != case["task_sha256"]: + raise ValueError("Candidate summary differs from its task manifest") + task_bytes = read_comparison_task( + [BENCH_ROOT, LAYOUT_POLICY_ROOT], task["path"], + expected_sha256=task["sha256"], + ) case_detail = fixed_case_data[case["task_id"]] if "task_sha256" in case_detail: if not case_detail["task_sha256"] == task["sha256"]: @@ -1434,6 +1445,11 @@ def timestamp(value): ], "macos_comparison": macos_comparison, "version_comparison": version_comparison, + "input_definition_variants": { + cid: {"baseline_sha256": selected[cid][1], "comparison_sha256": item["task_sha256"]} + for cid, item in fixed_case_data.items() + if item.get("task_sha256") and item["task_sha256"] != selected[cid][1] + }, "functional_methods": functional_methods, "fixed_candidates": fixed_candidates, "macos_resource": macos_resource, diff --git a/harness/lexbench/verification.py b/harness/lexbench/verification.py index 2240cf706..f0db83c0b 100644 --- a/harness/lexbench/verification.py +++ b/harness/lexbench/verification.py @@ -175,7 +175,7 @@ def validate(model, site, check_files=True): for language in languages: expected = pathlib.Path(directory) / f"{language}.svg" render_chart(combined, expected, ("memory", "cpu"), language) - actual = site / f"assets/moli-all-success-resources-{language}.svg" + actual = site / f"assets/comparison-all-success-resources-{language}.svg" if not actual.is_file() or actual.read_bytes() != expected.read_bytes(): fail("combined chart does not match measured registry") candidates = model.get("fixed_candidates") or {} @@ -518,7 +518,7 @@ def resource_document(info): ): fail("macOS chart metric or change") for lang in languages: - path = site / f"assets/moli-macos-success-resources-{lang}.svg" + path = site / f"assets/comparison-macos-success-resources-{lang}.svg" if ( not path.is_file() or "RSS" not in path.read_text() @@ -758,9 +758,9 @@ def resource_document(info): ): fail("version comparison data target") charts = { - "zh": (site / "assets/moli-version-success-resources-zh.svg", "372 题成功率与资源消耗"), + "zh": (site / "assets/comparison-version-success-resources-zh.svg", "372 题成功率与资源消耗"), "en": ( - site / "assets/moli-version-success-resources-en.svg", + site / "assets/comparison-version-success-resources-en.svg", "Success on 372 cases vs. resource use", ), } @@ -1031,16 +1031,16 @@ def main(argv=None): "--release", action="store_true", help="Require the complete bound method matrix" ) parser.add_argument( - "--profile", type=pathlib.Path, help="Release profile (defaults to the repository profile)" + "--profile", type=pathlib.Path, help="External release profile" ) args = parser.parse_args(argv) if args.profile is not None and not args.release: parser.error("--profile requires --release") profile = None if args.release: - profile_path = args.profile or ( - pathlib.Path(__file__).resolve().parents[2] / "evaluation/moli/current-report.json" - ) + if args.profile is None: + parser.error("--release requires --profile") + profile_path = args.profile if not profile_path.is_file(): parser.error("Release profile is unavailable; supply --profile") profile = json.loads(profile_path.read_bytes()) diff --git a/harness/reporting/assets.py b/harness/reporting/assets.py index 8329a1c5c..6b35ae249 100644 --- a/harness/reporting/assets.py +++ b/harness/reporting/assets.py @@ -164,3 +164,19 @@ def copy_bound_assets(source: Path, destination: Path, bindings: dict[str, str]) target.parent.mkdir(parents=True, exist_ok=True) with target.open('xb') as stream: stream.write(data) + + +def bound_json_asset(binding, *, root): + """Load an explicitly selected JSON asset; absent assets inherit no content.""" + if binding is None: + return None, {} + path = Path(binding['path']) + path = (unlinked_path(path) if path.is_absolute() else + contained_path(Path(root), binding['path'], reject_symlinks=True)) + raw = path.read_bytes() + if hashlib.sha256(raw).hexdigest() != binding.get('sha256'): + raise ValueError('Report JSON asset differs from its binding') + data = json.loads(raw) + if not isinstance(data, dict): + raise ValueError('Report JSON asset must be an object') + return path, data diff --git a/harness/reporting/locales.py b/harness/reporting/locales.py new file mode 100644 index 000000000..d618402a5 --- /dev/null +++ b/harness/reporting/locales.py @@ -0,0 +1,20 @@ +"""Read report messages from language catalogs.""" +import json +from functools import cache +from pathlib import Path + + +@cache +def _catalog(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def translate(path: Path, key: str, language: str, **values): + """Select one declared language; preserve values supplied by the report model.""" + try: + message = _catalog(path)[language][key] + except KeyError as exc: + raise ValueError(f"Missing report translation: {language}/{key}") from exc + if isinstance(message, list): + return [item.format_map(values) for item in message] + return message.format_map(values) diff --git a/harness/reporting/source_policy.py b/harness/reporting/source_policy.py new file mode 100644 index 000000000..0ffbbb1b4 --- /dev/null +++ b/harness/reporting/source_policy.py @@ -0,0 +1,38 @@ +"""Check the boundary between implementation and localized report content.""" +from __future__ import annotations + +import ast +import io +import re +import tokenize +from pathlib import Path + +HAN = re.compile(r"[\u3400-\u9fff]") +CATALOG_RENDERERS = ( + "scripts/moli_report/app.js", + "harness/trajectory_replay/interpretation.py", + "harness/web_fetch/methods.py", + "harness/web_fetch/report_interfaces.py", +) + + +def check_source_languages(root: Path) -> list[str]: + findings = [] + for directory in ("harness", "scripts"): + for path in sorted((root / directory).rglob("*.py")): + text = path.read_text(encoding="utf-8") + for token in tokenize.generate_tokens(io.StringIO(text).readline): + if token.type in (tokenize.COMMENT, tokenize.NAME) and HAN.search(token.string): + findings.append(f"{path.relative_to(root)}:{token.start[0]}: use English in code and comments") + for node in ast.walk(ast.parse(text)): + if isinstance(node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)): + if HAN.search(ast.get_docstring(node) or ""): + findings.append(f"{path.relative_to(root)}:{getattr(node, 'lineno', 1)}: use English in docstrings") + for relative in CATALOG_RENDERERS: + path = root / relative + if path.is_file() and HAN.search(path.read_text(encoding="utf-8")): + findings.append(f"{relative}: move report messages into the language catalog") + for name in ("current-report.json", "current-run.json", "current-comparison.json"): + for path in (root / "evaluation").rglob(name): + findings.append(f"{path.relative_to(root)}: keep report instances outside Git") + return findings diff --git a/harness/reporting/templates.py b/harness/reporting/templates.py index a08bf19d8..ce27783a0 100644 --- a/harness/reporting/templates.py +++ b/harness/reporting/templates.py @@ -12,19 +12,16 @@ def resolve_template(track, *, root=ROOT): if track not in registry["tracks"]: raise ValueError("Unregistered report track; add its maintained renderer, runnable example and release checks first") result = dict(registry["tracks"][track]) - for key in ("renderer", "entrypoint", "profile", "tests", *( - key for key in result if key.endswith('_profile') - )): + for key in ("renderer", "entrypoint", "tests"): path = root / result[key] if not path.is_file() or not path.resolve().is_relative_to(root.resolve()): raise ValueError("Report template has a missing or unsafe " + key) - profile = json.loads((root / result["profile"]).read_text()) if not result.get("example"): - publication = profile.get("publication", {}) - host = publication.get("host") or publication.get("base_url") - slug = publication.get("slug") or publication.get("site_slug") - version = publication.get("version_id") or publication.get("expected_version") - if not all((host, slug, version)): - raise ValueError("Report template needs a version-pinned example") - result["example"] = f"{host}/s/{slug}?version={version}" + raise ValueError("Report template needs a version-pinned example") return {"track": track, **result} + + +def template_identity(track): + """Record the maintained owner without borrowing an example's report inputs.""" + template = resolve_template(track) + return {key: template[key] for key in ("track", "renderer", "entrypoint")} diff --git a/harness/trajectory_replay/catalog_release.py b/harness/trajectory_replay/catalog_release.py index f838558a1..15e2fa7aa 100644 --- a/harness/trajectory_replay/catalog_release.py +++ b/harness/trajectory_replay/catalog_release.py @@ -21,7 +21,7 @@ def main() -> None: parser.add_argument( "--profile", type=Path, - default=Path("evaluation/trajectory-replay/webarena-verified-full/current-report.json"), + required=True, ) parser.add_argument( "--data-home", @@ -44,9 +44,7 @@ def main() -> None: parser.add_argument( "--comparison-release", type=Path, - default=Path( - "evaluation/trajectory-replay/webarena-verified-full/current-comparison.json" - ), + required=True, ) args = parser.parse_args() verify_webarena_runtime_source() diff --git a/harness/trajectory_replay/diagnostics.py b/harness/trajectory_replay/diagnostics.py index 0fb5bd918..f90e97b5d 100644 --- a/harness/trajectory_replay/diagnostics.py +++ b/harness/trajectory_replay/diagnostics.py @@ -625,7 +625,7 @@ def main_comparison_inputs(argv=None) -> None: parser.add_argument( "--profile", type=Path, - default=Path("evaluation/trajectory-replay/webarena-verified/current-run.json"), + required=True, ) parser.add_argument( "--corrections", diff --git a/harness/trajectory_replay/interpretation.py b/harness/trajectory_replay/interpretation.py index b0b672bdc..b3e6348d3 100644 --- a/harness/trajectory_replay/interpretation.py +++ b/harness/trajectory_replay/interpretation.py @@ -7,12 +7,14 @@ from pathlib import Path from urllib.parse import parse_qsl, urlencode, urljoin, urlsplit, urlunsplit +from harness.reporting.locales import translate from harness.trajectory_replay.failure_matrix import ( browser_failure_profile, failure_category_matrix, failure_signature_cards, ) +MESSAGES = Path(__file__).with_name("messages.json") def _report_page_url(report_url: str, relative: str) -> str: base = urlsplit(report_url) @@ -23,7 +25,6 @@ def _report_page_url(report_url: str, relative: str) -> str: def _outcome_chart(methods: dict, rows: list[dict], lang: str) -> str: """Render exact pass counts as a slide-friendly visual, not another data table.""" - zh = lang == "zh" entries = [] for key, method in methods.items(): selected = [row for row in rows if row["participant_id"] == key] @@ -36,7 +37,7 @@ def _outcome_chart(methods: dict, rows: list[dict], lang: str) -> str: f'
' ) - caption = "官方通过数/812 题" if zh else "Official passes / 812 tasks" + caption = translate(MESSAGES, "outcome_caption", lang) return ( f"""
    {''.join(entries)}""" f'
{caption}
' @@ -85,11 +86,7 @@ def render_interpretation(*, source: Path, contract_audit: Path, results_sha256: browser_failure_profile(methods, rows, lang, section["participant"]) ) elif section.get("data") == "contract_changes": - headers = ( - ("题目", "原合同", "改动及影响") - if lang == "zh" - else ("Task", "Original contract", "Change and effect") - ) + headers = translate(MESSAGES, "contract_columns", lang) body.append(_table(headers, [ [f'#{row["task_id"]}', row[f"before_{lang}"], row[f"after_{lang}"]] for row in audit["task_contract_changes"] @@ -105,12 +102,8 @@ def render_interpretation(*, source: Path, contract_audit: Path, results_sha256: raise ValueError("article figure is missing or symlinked") src = ("data:image/png;base64," + base64.b64encode(image.read_bytes()).decode() if standalone else image_prefix + filename) - figure_link = "下载原尺寸图" if lang == "zh" else "Download full-size diagram" - scroll_label = ( - "机制图,可横向滚动查看" - if lang == "zh" - else "Mechanism diagram, scroll horizontally to read" - ) + figure_link = translate(MESSAGES, "download_figure", lang) + scroll_label = translate(MESSAGES, "diagram_region", lang) download_href = ( _report_page_url(report_url, image_prefix + filename) if standalone else src @@ -151,7 +144,7 @@ def localized(value): if set(citation["label"]) != {"zh", "en"}: raise ValueError("article source requires both languages") links.append(f'{e(citation["label"][lang])}') - label = "源码与测试" if lang == "zh" else "Source and tests" + label = translate(MESSAGES, "source_and_tests", lang) body.append(f'

{label}: ' + " · ".join(links) + '

') body.append("
") body.append("") diff --git a/harness/trajectory_replay/messages.json b/harness/trajectory_replay/messages.json new file mode 100644 index 000000000..d8f63b202 --- /dev/null +++ b/harness/trajectory_replay/messages.json @@ -0,0 +1,24 @@ +{ + "en": { + "outcome_caption": "Official passes / tasks per configuration", + "contract_columns": [ + "Task", + "Original contract", + "Change and effect" + ], + "download_figure": "Download full-size diagram", + "diagram_region": "Mechanism diagram, scroll horizontally to read", + "source_and_tests": "Source and tests" + }, + "zh": { + "outcome_caption": "官方通过数/各配置任务数", + "contract_columns": [ + "题目", + "原合同", + "改动及影响" + ], + "download_figure": "下载原尺寸图", + "diagram_region": "机制图,可横向滚动查看", + "source_and_tests": "源码与测试" + } +} diff --git a/harness/trajectory_replay/public_webarena/publication.py b/harness/trajectory_replay/public_webarena/publication.py index bd496d3c0..da0f44933 100644 --- a/harness/trajectory_replay/public_webarena/publication.py +++ b/harness/trajectory_replay/public_webarena/publication.py @@ -375,6 +375,7 @@ def package_main(project_root: Path) -> None: description="Build the standalone fixed-trajectory report bundle." ) parser.add_argument("--formal-root", type=Path, default=default_formal_root) + parser.add_argument("--profile", type=Path, required=True) parser.add_argument( "--archive", type=Path, default=project_root / "publish/webarena-fixed-trajectory.zip" ) @@ -390,7 +391,7 @@ def package_main(project_root: Path) -> None: args.formal_root, args.archive, args.attestation, - authority=project_root / "evaluation/browser-use-public/current-report.json", + authority=args.profile, ), ensure_ascii=False, ) diff --git a/harness/trajectory_replay/public_webarena/release.py b/harness/trajectory_replay/public_webarena/release.py index 2b57ec2de..41f1995ac 100644 --- a/harness/trajectory_replay/public_webarena/release.py +++ b/harness/trajectory_replay/public_webarena/release.py @@ -48,7 +48,6 @@ def _verification_command(publication: dict, archive: Path, project_root: Path) def main(project_root: Path) -> None: ROOT = project_root.resolve() - CURRENT = ROOT / "evaluation/browser-use-public/current-report.json" ARCHIVE = ROOT / "publish/webarena-fixed-trajectory.zip" ATTESTATION = ROOT / "publish/webarena-fixed-trajectory-attestation.json" RECEIPT = ROOT / "publish/webarena-fixed-trajectory-site-receipt.json" @@ -60,7 +59,9 @@ def main(project_root: Path) -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--formal-root", type=Path, default=DEFAULT_FORMAL) parser.add_argument("--publish", action="store_true") + parser.add_argument("--profile", type=Path, required=True) args = parser.parse_args() + CURRENT = args.profile if args.publish: recover_binding(CURRENT, ATTESTATION, BINDING, schema="public_webarena_binding/1") current = json.loads(CURRENT.read_text(encoding="utf-8")) diff --git a/harness/trajectory_replay/report_compaction.py b/harness/trajectory_replay/report_compaction.py index eb87728c3..5a0af9276 100644 --- a/harness/trajectory_replay/report_compaction.py +++ b/harness/trajectory_replay/report_compaction.py @@ -4,10 +4,13 @@ from __future__ import annotations +import html import json +import posixpath import re import shutil from pathlib import Path +from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit SHARD_COUNT = 16 @@ -19,33 +22,30 @@ def _task_id(value: str) -> int: return int(match.group(1)) -def _rewrite_links(text: str, *, detail_kind: str | None = None) -> str: - text = re.sub( - r'href="(?:\.\./)?task/(\d+)\.html([#?][^"]*)?"', - lambda match: f'href="task.html?id={match.group(1)}{match.group(2) or ""}"', - text, - ) - text = re.sub( - r'href="(?:\.\./)?result/([A-Za-z0-9_.-]+)\.html([#?][^"]*)?"', - lambda match: f'href="result.html?key={match.group(1)}{match.group(2) or ""}"', - text, - ) - if detail_kind == "result": - text = re.sub( - r'href="([A-Za-z0-9_.-]+-\d+)\.html([#?][^"]*)?"', - lambda match: f'href="result.html?key={match.group(1)}{match.group(2) or ""}"', - text, - ) - text = re.sub( - r'href="(?:\.\./)*data/evidence/([A-Za-z0-9_.-]+)\.json"', - lambda match: f'href="evidence-raw.html?key={match.group(1)}"', - text, - ) - return ( - text.replace('="../', '="') - .replace(">下载净化 JSON<", ">查看净化 JSON<") - .replace(">Download sanitized JSON<", ">Inspect sanitized JSON<") - ) +def _rewrite_links(text: str, *, detail_kind: str | None = None, + source_route: str = 'index.html', target_route: str = 'index.html') -> str: + def rewrite(match): + name, raw = match[1], html.unescape(match[2]) + url = urlsplit(raw) + if url.scheme or url.netloc or not url.path or raw.startswith('/'): + return match[0] + resolved = posixpath.normpath(posixpath.join(posixpath.dirname(source_route), url.path)) + target = resolved + query = parse_qsl(url.query, keep_blank_values=True) + route = re.fullmatch(r'((?:en/)?)task/(\d+)[.]html', resolved) + result = re.fullmatch(r'((?:en/)?)result/([A-Za-z0-9_.-]+)[.]html', resolved) + evidence = re.fullmatch(r'data/evidence/([A-Za-z0-9_.-]+)[.]json', resolved) + if route or result: + item = route or result + target = item[1] + ('task.html' if route else 'result.html') + query.insert(0, ('id' if route else 'key', item[2])) + elif evidence: + target = ('en/' if source_route.startswith('en/') else '') + 'evidence-raw.html' + query.insert(0, ('key', evidence[1])) + relative = posixpath.relpath(target, posixpath.dirname(target_route) or '.') + return name + '="' + html.escape(urlunsplit(('', '', relative, urlencode(query), url.fragment)), quote=True) + '"' + return re.sub(r'\b(href|src)="([^"]*)"', rewrite, text).replace( + '>下载净化 JSON<', '>查看净化 JSON<').replace('>Download sanitized JSON<', '>Inspect sanitized JSON<') def _javascript_source(source: str) -> str: @@ -55,9 +55,11 @@ def _javascript_source(source: str) -> str: return source -def _loader(kind: str) -> str: +def _loader(kind: str, lang: str = "zh") -> str: parameter = "id" if kind == "task" else "key" - return _javascript_source(f'''轨迹重放详情

正在读取详情…

''') -def _raw_evidence_loader() -> str: - return _javascript_source(f'''单结果净化 JSON

返回结果

单结果净化 JSON

正在读取…
''') def _data_fetch_adapter() -> str: return _javascript_source(rf'''(()=>{{ const originalFetch=window.fetch.bind(window);const compactCache=new Map(); +const compactBase=new URL("../",document.currentScript.src); const compactRoute=raw=>{{ const url=new URL(raw,location.href);const match=url.pathname.match(/data\/(trajectory|evidence)\/([A-Za-z0-9_.-]+)\.json$/); if(!match)return null; @@ -102,7 +109,7 @@ def _data_fetch_adapter() -> str: const route=compactRoute(typeof input==='string'?input:input.url); if(!route)return originalFetch(input,init); const cacheKey=route.kind+'-'+route.shard; - if(!compactCache.has(cacheKey))compactCache.set(cacheKey,originalFetch('data/'+route.kind+'-'+route.shard+'.json').then(response=>{{if(!response.ok)throw Error(`HTTP ${{response.status}}`);return response.json()}})); + if(!compactCache.has(cacheKey))compactCache.set(cacheKey,originalFetch(new URL('data/'+route.kind+'-'+route.shard+'.json',compactBase)).then(response=>{{if(!response.ok)throw Error(`HTTP ${{response.status}}`);return response.json()}})); const value=(await compactCache.get(cacheKey))[route.key]; return value===undefined?new Response('',{{status:404}}):new Response(JSON.stringify(value),{{status:200,headers:{{'content-type':'application/json'}}}}); }}; @@ -120,94 +127,138 @@ def compact_report(source: Path, destination: Path) -> dict[str, int]: if destination.exists(): raise FileExistsError(destination) destination.mkdir(parents=True) - excluded = {"task", "result"} - for path in source.rglob("*"): + manifest = json.loads((source / 'report-manifest.json').read_text()) + languages = manifest.get('languages') or ['zh'] + prefixes = ['' if language == 'zh' else language + '/' for language in languages] + for path in source.rglob('*'): if not path.is_file(): continue - relative = path.relative_to(source) - if relative.parts[0] in excluded or relative.parts[:2] in { - ("data", "trajectory"), - ("data", "evidence"), - }: + relative = path.relative_to(source).as_posix() + if any(relative.startswith(prefix + kind + '/') for prefix in prefixes for kind in ('task', 'result')): + continue + if relative.startswith(('data/trajectory/', 'data/evidence/')): continue target = destination / relative target.parent.mkdir(parents=True, exist_ok=True) - if path.suffix == ".html": - target.write_text(_rewrite_links(path.read_text(encoding="utf-8")), encoding="utf-8") + if path.suffix == '.html': + target.write_text(_rewrite_links(path.read_text(), source_route=relative, target_route=relative)) else: shutil.copy2(path, target) - - pages = destination / "pages" - pages.mkdir() - for kind in ("task", "result"): - buckets = [{} for _ in range(SHARD_COUNT)] - for path in sorted((source / kind).glob("*.html")): - key = path.stem - buckets[_task_id(key) % SHARD_COUNT][key] = _rewrite_links( - path.read_text(encoding="utf-8"), detail_kind=kind - ) - for index, rows in enumerate(buckets): - (pages / f"{kind}-{index:02d}.json").write_text( - json.dumps(rows, ensure_ascii=False, separators=(",", ":")), encoding="utf-8" - ) - (destination / f"{kind}.html").write_text(_loader(kind), encoding="utf-8") - (destination / "evidence-raw.html").write_text( - _raw_evidence_loader(), encoding="utf-8" - ) - - (destination / "data").mkdir(exist_ok=True) - for kind in ("trajectory", "evidence"): + for prefix in prefixes: + language = 'en' if prefix else 'zh' + root = destination / prefix + pages = root / 'pages' + pages.mkdir() + for kind in ('task', 'result'): + buckets = [{} for _ in range(SHARD_COUNT)] + for path in sorted((source / prefix / kind).glob('*.html')): + key = path.stem + buckets[_task_id(key) % SHARD_COUNT][key] = _rewrite_links( + path.read_text(), source_route=path.relative_to(source).as_posix(), + target_route=prefix + kind + '.html') + for index, rows in enumerate(buckets): + (pages / f'{kind}-{index:02d}.json').write_text(json.dumps(rows, ensure_ascii=False, separators=(',', ':'))) + (root / f'{kind}.html').write_text(_loader(kind, language)) + (root / 'evidence-raw.html').write_text(_raw_evidence_loader(language)) + (destination / 'data').mkdir(exist_ok=True) + for kind in ('trajectory', 'evidence'): shards = [{} for _ in range(SHARD_COUNT)] - for path in sorted((source / f"data/{kind}").glob("*.json")): - key = path.stem - shards[_task_id(key) % SHARD_COUNT][key] = json.loads( - path.read_text(encoding="utf-8") - ) + for path in sorted((source / f'data/{kind}').glob('*.json')): + shards[_task_id(path.stem) % SHARD_COUNT][path.stem] = json.loads(path.read_text()) for index, rows in enumerate(shards): - (destination / f"data/{kind}-{index:02d}.json").write_text( - json.dumps(rows, ensure_ascii=False, separators=(",", ":")), encoding="utf-8" - ) - app = destination / "assets/app.js" - app.write_text(_data_fetch_adapter() + app.read_text(encoding="utf-8"), encoding="utf-8") - files = [path for path in destination.rglob("*") if path.is_file()] - return { - "files": len(files), - "task_routes": len(list((source / "task").glob("*.html"))), - "result_routes": len(list((source / "result").glob("*.html"))), - } - - -def validate_compact_report(report: Path, *, max_files: int) -> dict[str, int]: - files = [path for path in report.rglob("*") if path.is_file()] + (destination / f'data/{kind}-{index:02d}.json').write_text(json.dumps(rows, ensure_ascii=False, separators=(',', ':'))) + app = destination / 'assets/app.js' + app.write_text(_data_fetch_adapter() + app.read_text()) + return {'files': sum(path.is_file() for path in destination.rglob('*')), + 'task_routes': len(list((source / 'task').glob('*.html'))), + 'result_routes': len(list((source / 'result').glob('*.html')))} + + +def validate_compact_report(report: Path, *, max_files: int, source: Path | None = None) -> dict[str, int]: + files = [path for path in report.rglob('*') if path.is_file()] if not files or len(files) > max_files: - raise ValueError(f"compact report file count is outside 1..{max_files}: {len(files)}") - required = [ - report / "index.html", - report / "task.html", - report / "result.html", - report / "evidence-raw.html", - ] - required += [ - report / f"pages/{kind}-{index:02d}.json" - for kind in ("task", "result") - for index in range(SHARD_COUNT) - ] - required += [ - report / f"data/{kind}-{index:02d}.json" - for kind in ("trajectory", "evidence") - for index in range(SHARD_COUNT) - ] - if any(not path.is_file() for path in required): - raise ValueError("compact report route bundles are incomplete") - manifest = json.loads((report / "report-manifest.json").read_text(encoding="utf-8")) - task_routes = sum( - len(json.loads((report / f"pages/task-{index:02d}.json").read_text())) - for index in range(SHARD_COUNT) - ) - result_routes = sum( - len(json.loads((report / f"pages/result-{index:02d}.json").read_text())) - for index in range(SHARD_COUNT) - ) - if task_routes != manifest["task_count"] or result_routes != manifest["logical_results"]: - raise ValueError("compact report route coverage differs from its manifest") - return {"files": len(files), "task_routes": task_routes, "result_routes": result_routes} + raise ValueError(f'compact report file count is outside 1..{max_files}: {len(files)}') + manifest = json.loads((report / 'report-manifest.json').read_text()) + prefixes = ['' if lang == 'zh' else lang + '/' for lang in manifest.get('languages') or ['zh']] + for prefix in prefixes: + for kind, field in [('task', 'task_count'), ('result', 'logical_results')]: + root = report / prefix + if not (root / f'{kind}.html').is_file() or not (root / 'evidence-raw.html').is_file(): + raise ValueError('compact report route bundles are incomplete') + values = {} + for index in range(SHARD_COUNT): + path = root / f'pages/{kind}-{index:02d}.json' + if not path.is_file(): + raise ValueError('compact report route bundles are incomplete') + bucket = json.loads(path.read_text()) + if values.keys() & bucket.keys(): + raise ValueError('duplicate compact logical route') + values.update(bucket) + if len(values) != manifest[field]: + raise ValueError('compact report route coverage differs from its manifest') + if source is not None: + expected = {p.stem: _rewrite_links(p.read_text(), source_route=p.relative_to(source).as_posix(), + target_route=prefix + kind + '.html') for p in (source / prefix / kind).glob('*.html')} + if values != expected: + raise ValueError('compact report pages differ from validated source') + for kind in ('trajectory', 'evidence'): + values = {} + for index in range(SHARD_COUNT): + path = report / f'data/{kind}-{index:02d}.json' + if not path.is_file(): + raise ValueError('compact evidence bundles are incomplete') + bucket = json.loads(path.read_text()) + if values.keys() & bucket.keys(): + raise ValueError('duplicate compact evidence key') + values.update(bucket) + if source is not None: + expected = {p.stem: json.loads(p.read_text()) for p in (source / f'data/{kind}').glob('*.json')} + if values != expected: + raise ValueError('compact evidence differs from validated source') + return {'files': len(files), 'task_routes': manifest['task_count'], 'result_routes': manifest['logical_results']} + + +def check_compact_navigation(report: Path) -> None: + """Exercise the exported query routes and evidence, in every saved language.""" + from playwright.sync_api import sync_playwright + + from harness.reporting.visual import browser_executable, local_report_server + + manifest = json.loads((report / 'report-manifest.json').read_text()) + rows = json.loads((report / 'data/results.json').read_text()) + samples = [next((row for row in rows if (row['status'] == 'pass') == passed), None) + for passed in (True, False)] + with local_report_server(report) as origin, sync_playwright() as playwright: + browser = playwright.chromium.launch(executable_path=browser_executable(), headless=True) + try: + for language in manifest.get('languages') or ['zh']: + prefix = '' if language == 'zh' else language + '/' + page = browser.new_page() + for row in samples: + if row is None: + continue + page.goto(f"{origin}/{prefix}result.html?key={row['key']}") + page.locator('header.top').wait_for() + if page.locator('html').get_attribute('lang') != language: + raise ValueError('compact result language differs') + step = page.locator('button[data-open-step]').first + if step.count(): + step.click() + page.locator('#step-dialog[open]').wait_for() + page.wait_for_function("() => document.querySelector('[data-step-planned]')?.textContent.includes('action')") + page.keyboard.press('Escape') + page.goto(f"{origin}/{prefix}evidence-raw.html?key={row['key']}") + page.wait_for_function("() => document.querySelector('pre')?.textContent.trim().startsWith('{')") + observed = json.loads(page.locator('pre').inner_text()) + expected = json.loads((report / f"data/evidence-{int(row['task_id']) % SHARD_COUNT:02d}.json").read_text())[row['key']] + if observed != expected: + raise ValueError('compact served evidence differs from its bound payload') + page.goto(f"{origin}/{prefix}task.html?id={row['task_id']}") + page.locator('header.top').wait_for() + page.locator('a[href*="result.html?key="]').first.click() + page.locator('header.top').wait_for() + page.locator('nav a[href*="index.html"]').first.click() + page.locator('header.top').wait_for() + page.close() + finally: + browser.close() diff --git a/harness/trajectory_replay/webarena_report.py b/harness/trajectory_replay/webarena_report.py index 31925b615..0e72c0eea 100644 --- a/harness/trajectory_replay/webarena_report.py +++ b/harness/trajectory_replay/webarena_report.py @@ -18,12 +18,14 @@ from harness.record_io import load_jsonl from harness.reporting.privacy import LOCAL_PATH_RE, WEB_URL_RE +from harness.reporting.templates import template_identity from harness.trajectory_replay.catalog_contracts import ( LEGACY_CATALOG_CONTRACTS_SCHEMA, LEGACY_CATALOG_REPORT_SCHEMA, ) from harness.trajectory_replay.evaluator_transition import json_sha256, reevaluate_pass from harness.trajectory_replay.failure_matrix import ( + browser_failure_profile, failure_category_matrix, failure_signature_cards, ) @@ -337,7 +339,10 @@ def _supplemental_diagnostic_html(diagnostic: dict | None, lang: str) -> str: network_rows = [] header_rows = [] for method in diagnostic["observations"]: - label = {"release": "Moli 1.1.8", "pr": "Moli · PR 640"}.get(method["method"], method["method"]) + label = method.get("label") or method.get("version") or { + "release": "正式构建" if zh else "Release build", + "pr": "候选构建" if zh else "Candidate build", + }.get(method["method"], method["method"]) for sample in method["observations"]: for request in sample["state"].get("headerRecording", []): cookie_label = ("有" if request["cookie_present"] else "无") if zh else ("Yes" if request["cookie_present"] else "No") @@ -346,7 +351,7 @@ def _supplemental_diagnostic_html(diagnostic: dict | None, lang: str) -> str: request["wire_referer_path"] or missing, request["har_referer_path"] or missing) header_rows.append("" + "".join(f"{_e(value)}" for value in values) + "") for request in sample["state"].get("httpReads", []): - values = (label, request["path"], request.get("status", "—"), + values = (label, request.get("path") or request.get("url") or ("未记录" if zh else "Not recorded"), request.get("status", "—"), request.get("bytes", "—"), request.get("error", "")) network_rows.append("" + "".join(f"{_e(value)}" for value in values) + "") readiness = sample["state"].get("readiness") @@ -854,7 +859,7 @@ def _detail_page( report_root = "../" if zh else "../../" stage_labels = { "browser_session_setup": ("浏览器会话初始化", "Browser session setup"), - "browser_tool_execution": ("Playwright 动作执行", "Playwright action execution"), + "browser_tool_execution": ("浏览器动作执行", "Browser action execution"), "evidence_finalization": ("执行证据收尾", "Evidence finalization"), "official_evaluator_execution": ("官方评测器执行", "Official evaluator execution"), "side_effect_not_observed": ("网络验收条件未满足", "Network acceptance not satisfied"), @@ -1248,7 +1253,7 @@ def _assets(output: Path) -> None: if(!result)return; const detail=result.detail_url||'result/'+result.key+'.html'; dialog.querySelector('[data-dialog-title]').textContent=result.browser+' · '+(zh?'任务':'Task')+' #'+result.task_id; - dialog.querySelector('[data-dialog-stage]').textContent=result.failure_stage==='browser_session_setup'?(zh?'浏览器会话初始化':'Browser session setup'):result.failure_stage==='browser_tool_execution'?(zh?'Playwright 动作':'Playwright action'):result.failure_stage==='evidence_finalization'?(zh?'执行证据收尾':'Evidence finalization'):result.failure_stage==='official_evaluator_execution'?(zh?'官方评测器执行':'Official evaluator execution'):result.failure_stage==='side_effect_not_observed'?(zh?'网络验收条件未满足':'Network acceptance not satisfied'):result.failure_stage==='official_evaluation'?(zh?'官方验收':'Official evaluation'):(zh?'无':'None'); + dialog.querySelector('[data-dialog-stage]').textContent=result.failure_stage==='browser_session_setup'?(zh?'浏览器会话初始化':'Browser session setup'):result.failure_stage==='browser_tool_execution'?(zh?'浏览器动作':'Browser action'):result.failure_stage==='evidence_finalization'?(zh?'执行证据收尾':'Evidence finalization'):result.failure_stage==='official_evaluator_execution'?(zh?'官方评测器执行':'Official evaluator execution'):result.failure_stage==='side_effect_not_observed'?(zh?'网络验收条件未满足':'Network acceptance not satisfied'):result.failure_stage==='official_evaluation'?(zh?'官方验收':'Official evaluation'):(zh?'无':'None'); dialog.querySelector('[data-dialog-error]').textContent=zh?'正在读取完整证据…':'Loading complete evidence…'; delete dialog.dataset.loaded; dialog.querySelector('[data-dialog-link]').href=detail; @@ -1362,6 +1367,7 @@ def _assets(output: Path) -> None: ".interpretation>.presentation-slide{margin:30px 0;padding:clamp(24px,4vw,50px);background:var(--panel);border:1px solid var(--line);border-radius:24px;box-shadow:var(--shadow)}.interpretation>.presentation-slide>h2{max-width:980px;font-size:clamp(1.7rem,3vw,2.7rem);margin-top:0}" ".outcome-chart ol{list-style:none;padding:0;margin:24px 0;display:grid;gap:17px}.outcome-chart li>div:first-child{display:flex;justify-content:space-between;gap:18px}.outcome-chart li strong{font-size:1.08rem}.outcome-track{height:18px;margin-top:7px}.outcome-chart figcaption{text-align:right;color:var(--muted)}" ".browser-failure-profile{display:grid;gap:22px;margin:26px 0}.browser-profile-overview{display:grid;grid-template-columns:1.2fr 1fr;gap:14px}.browser-profile-overview>div{border:1px solid var(--line);border-radius:16px;padding:20px;background:var(--bg)}.browser-profile-overview span,.browser-profile-overview small{display:block}.browser-profile-overview strong{display:block;font-size:clamp(2rem,5vw,4rem);line-height:1.05;margin:8px 0}" + ".failure-bars li>*{min-width:0;overflow-wrap:anywhere}.failure-explanations article{min-width:0;overflow-wrap:anywhere}.interpretation{min-width:0;max-width:100%}.interpretation p{overflow-wrap:anywhere}" ".failure-bars{list-style:none;padding:0;margin:0;display:grid;gap:9px}.failure-bars li{display:grid;grid-template-columns:minmax(180px,1.4fr) minmax(180px,2fr) 54px 58px;gap:12px;align-items:center}.failure-bars strong,.failure-bars small{text-align:right}.failure-explanations{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.failure-explanations article{border:1px solid var(--line);border-radius:15px;padding:18px;background:var(--bg)}.failure-explanations header{display:flex;justify-content:space-between;gap:14px}.failure-explanations h3{font-size:1.05rem;margin:0}.failure-explanations p{font-size:.94rem}.failure-explanations a{font-size:.9rem}" "\n.diagnostic-observations table{min-width:840px}" ".diagnostic-observations td:first-child{white-space:nowrap;min-width:100px}" @@ -1370,7 +1376,7 @@ def _assets(output: Path) -> None: ".diagnostic-timeline li{border:1px solid #888;border-radius:10px;padding:12px;min-width:0}" ".diagnostic-timeline strong,.diagnostic-timeline span{display:block}" ".fixed36-diagnostic figure img{display:block;max-width:100%;height:auto;border:1px solid var(--line);border-radius:8px}" - "@media(max-width:760px){.section-heading{display:block}.section-heading>a{display:inline-block;margin-top:12px}.failure-signature-grid,.failure-explanations,.browser-profile-overview{grid-template-columns:1fr}.failure-bars li{grid-template-columns:1fr 52px}.failure-bars li .failure-bar-track{grid-column:1/-1;grid-row:2}.failure-bars li small{grid-column:2}.interpretation>.presentation-slide{padding:22px 18px}}" + "@media(max-width:760px){.section-heading{display:block}.section-heading>a{display:inline-block;margin-top:12px}.failure-signature-grid,.failure-explanations,.browser-profile-overview{grid-template-columns:1fr}.failure-bars li{grid-template-columns:minmax(0,1fr) 52px}.failure-bars li .failure-bar-track{grid-column:1/-1;grid-row:2}.failure-bars li small{grid-column:2}.interpretation>.presentation-slide{padding:22px 18px}}" "@media print{@page{size:13.333in 7.5in;margin:.42in}.interpretation>.hero,.interpretation>.presentation-slide{break-after:page;break-inside:avoid;box-shadow:none;border-color:#bbb}.failure-explanations article{break-inside:avoid}}" ) (output / "assets/report.css").write_text(css, encoding="utf-8") @@ -1695,6 +1701,13 @@ def build_webarena_replay_report( if zh else f"WebArena-Verified · {task_count} tasks · model calls 0" ) + backend_note = ( + ("所有方法通过同一 Playwright 重放器执行冻结动作;没有 Agent 决策或模型调用。" + if zh else "All methods use the same Playwright replay adapter for frozen actions, with no Agent decisions or model calls.") + if execution_backend == "playwright" else + ("所有方法重放冻结动作,没有 Agent 决策或模型调用。历史配置没有声明执行后端,报告不推定其具体实现。" + if zh else "All methods replay frozen actions without Agent decisions or model calls. The historical profile does not declare an execution backend.") + ) failure_participants = tuple( engine for engine in methods @@ -1735,15 +1748,15 @@ def build_webarena_replay_report( ) core_metrics_note += ( ( - "提前失败会缩短工作量,因此总耗时和资源不按低者判优;单次动作耗时按实际 Playwright 调用统计。" + "提前失败会缩短工作量,因此总耗时和资源不按低者判优;单次动作耗时按实际浏览器动作调用统计。" if has_resource_samples - else "提前失败会缩短工作量,因此总耗时不按低者判优;单次动作耗时按实际 Playwright 调用统计。" + else "提前失败会缩短工作量,因此总耗时不按低者判优;单次动作耗时按实际浏览器动作调用统计。" ) if zh else ( - "Early failures reduce work, so total time and resources are not ranked by lower values; per-action latency uses actual Playwright calls." + "Early failures reduce work, so total time and resources are not ranked by lower values; per-action latency uses actual browser action calls." if has_resource_samples - else "Early failures reduce work, so total time is not ranked by lower values; per-action latency uses actual Playwright calls." + else "Early failures reduce work, so total time is not ranked by lower values; per-action latency uses actual browser action calls." ) ) core_metrics_table = _metrics_table( @@ -1758,7 +1771,7 @@ def build_webarena_replay_report( if resource_table else f'

{"资源数据" if zh else "Resource data"}

{"本轮没有记录进程级 CPU、内存和网络采样,因此这些指标不可用,报告不展示空值表或资源排名。端到端耗时和动作耗时来自现有轨迹记录,无需重新执行任务。" if zh else "This run did not record process-level CPU, memory, or network samples. Those metrics are unavailable, so the report does not show empty tables or resource rankings. End-to-end and action latency come from the retained trajectories and require no rerun."}

' ) - summary_body = f'''

{eyebrow}

{"浏览器轨迹重放评测" if zh else "Browser Trajectory Replay"}

{f"{browser_count} 款浏览器组成 {method_count} 种方法配置,分别覆盖同一 {task_count} 题的冻结轨迹重放。六种方法均由同一 Playwright 重放器通过 CDP 执行 BrowserTool 动作契约;本轮没有调用 CowAgent BrowserTool,也没有 Agent 或模型参与。结果比较的是 Playwright 重放器与浏览器的组合配置,不能单独归因于浏览器内核。方法版本、轨迹与站点准备见环境说明。" if zh else f"{browser_count} browsers form {method_count} method configurations, each covering the same {task_count} frozen trajectory replays. All six methods use the same Playwright replay adapter over CDP to execute the BrowserTool action contract; this run does not invoke CowAgent BrowserTool, an Agent, or a model. Results compare Playwright-adapter–browser configurations, not browser engines in isolation. Environment documents versions, paths and site preparation."}

+ summary_body = f'''

{eyebrow}

{"浏览器轨迹重放评测" if zh else "Browser Trajectory Replay"}

{f"{browser_count} 款浏览器组成 {method_count} 种方法配置,分别覆盖同一 {task_count} 题的冻结轨迹重放。{backend_note} 方法版本、轨迹与站点准备见环境说明。" if zh else f"{browser_count} browsers form {method_count} method configurations, each covering the same {task_count} frozen trajectory replays. {backend_note} Environment documents versions, paths and site preparation."}

{validity_notice} {f'

{"查看 74 条历史取数验收输入的逐条哈希" if zh else "Inspect hashes for all 74 historical retrieval inputs"}

' if legacy_provenance is not None else ''}

{"核心指标" if zh else "Core metrics"}

{core_metrics_note}

{core_metrics_table}
@@ -1796,12 +1809,12 @@ def build_webarena_replay_report( for row in public_rows: cause = row["cause_zh"] if zh else row["cause_en"] status = _verdict_label(row["status"], row.get("semantic_status"), lang) - stage_label = {"browser_session_setup": "浏览器会话初始化" if zh else "Browser session setup", "browser_tool_execution": "Playwright 动作" if zh else "Playwright action", "evidence_finalization": "执行证据收尾" if zh else "Evidence finalization", "official_evaluator_execution": "官方评测器执行" if zh else "Official evaluator execution", "side_effect_not_observed": "网络验收条件未满足" if zh else "Network acceptance not satisfied", "official_evaluation": "官方验收" if zh else "Official evaluation", "pass": "—"}.get(row["failure_stage"], row["failure_stage"]) + stage_label = {"browser_session_setup": "浏览器会话初始化" if zh else "Browser session setup", "browser_tool_execution": "浏览器动作" if zh else "Browser action", "evidence_finalization": "执行证据收尾" if zh else "Evidence finalization", "official_evaluator_execution": "官方评测器执行" if zh else "Official evaluator execution", "side_effect_not_observed": "网络验收条件未满足" if zh else "Network acceptance not satisfied", "official_evaluation": "官方验收" if zh else "Official evaluation", "pass": "—"}.get(row["failure_stage"], row["failure_stage"]) detail_url = row.get("detail_url") or f'result/{row["key"]}.html' result_rows.append(f'#{row["task_id"]}{_e(row["browser"])}{_e(row["sites"][0] if row["sites"] else "")}{status}{row["completed_actions"]}/{row["action_count"]}{_e(stage_label)}{_e(cause) if cause else "—"}{"证据页" if zh else "Evidence page"}') browser_options = "".join(f'' for key, item in comparison_methods.items()) site_options = "".join(f'' for site in sorted({s for row in public_rows for s in row["sites"]})) - stage_labels = {"browser_session_setup": ("浏览器会话初始化", "Browser session setup"), "browser_tool_execution": ("Playwright 动作", "Playwright action"), "evidence_finalization": ("执行证据收尾", "Evidence finalization"), "official_evaluator_execution": ("官方评测器执行", "Official evaluator execution"), "side_effect_not_observed": ("网络验收条件未满足", "Network acceptance not satisfied"), "official_evaluation": ("官方验收", "Official evaluation")} + stage_labels = {"browser_session_setup": ("浏览器会话初始化", "Browser session setup"), "browser_tool_execution": ("浏览器动作", "Browser action"), "evidence_finalization": ("执行证据收尾", "Evidence finalization"), "official_evaluator_execution": ("官方评测器执行", "Official evaluator execution"), "side_effect_not_observed": ("网络验收条件未满足", "Network acceptance not satisfied"), "official_evaluation": ("官方验收", "Official evaluation")} present_stages = {row["failure_stage"] for row in public_rows if row["status"] == "fail"} stage_options = "".join(f'' for code in stage_labels if code in present_stages) causes = {row["cause_zh"]: row["cause_zh"] if zh else row["cause_en"] for row in public_rows if row["cause_zh"]} @@ -1834,8 +1847,8 @@ def build_webarena_replay_report( results_body = f'''

{len(public_rows)} {"条逻辑结果" if zh else "logical outcomes"}

{"结果与失败诊断" if zh else "Results and failure diagnosis"}

{"同题对比支持选择基准和多个方法;下方筛选表默认只显示失败,点击“诊断”查看原始判定、复核原因与执行证据。" if zh else "Choose a baseline and multiple methods for same-task comparison. The inventory starts with failures; open Diagnose for the exact finding, reviewed cause, and execution evidence."}

{result_matrix}

{''.join(result_rows)}
{"任务" if zh else "Task"}{"方法" if zh else "Method"}{"站点" if zh else "Site"}{"判定" if zh else "Verdict"}{"动作" if zh else "Actions"}{"失败环节" if zh else "Failure stage"}{"复核分类" if zh else "Reviewed category"}{"详情" if zh else "Detail"}
{"失败环节" if zh else "Failure stage"}
{"动作定位" if zh else "Action locator"}
{"复核分类" if zh else "Reviewed category"}

{"程序判定与验收规则" if zh else "Programmatic finding and evaluator rule"}

{"复核原因与处理" if zh else "Reviewed cause and treatment"}

''' diagnostic_details = ( failure_category_matrix(comparison_methods, public_rows, lang) - + f'

{"Playwright 动作耗时" if zh else "Playwright action latency"}

' - + f'

{"均值、P50、P95 按单次 Playwright 调用统计;调用次数只说明工作量。逐题详情保留每个动作的耗时和返回内容。" if zh else "Mean, P50, and P95 are per Playwright call; call counts describe workload. Result details retain every action duration and response."}

' + + f'

{"浏览器动作耗时" if zh else "Browser action latency"}

' + + f'

{"均值、P50、P95 按单次浏览器动作调用统计;调用次数只说明工作量。逐题详情保留每个动作的耗时和返回内容。" if zh else "Mean, P50, and P95 are per browser action call; call counts describe workload. Result details retain every action duration and response."}

' + f'
{"展开各类动作耗时" if zh else "Open latency by action type"}{action_table}
' + f'

{"各方法成功任务的条件分布" if zh else "Conditional distributions on each method’s successful tasks"}

' + f'

{"各列仅含该方法成功的任务,任务集合和样本数可能不同。这组数据用于诊断,不参与方法排名。" if zh else "Each column contains only that method’s successful tasks. Task sets and sample counts may differ, so these distributions are diagnostic and not ranked."}

' @@ -1869,7 +1882,7 @@ def build_webarena_replay_report( V --> X[{"绑定证据的结果" if zh else "Evidence-bound result"}] X --> T[{"销毁浏览器与站点" if zh else "Teardown browser and sites"}] T --> R''' - environment_body = f'''

{"环境与评测流程" if zh else "Environment and evaluation flow"}

{"192.168.5.2 是 Colima 虚拟机中的宿主网关:浏览器容器通过它访问运行在同一台 Mac 虚拟化环境里的 WebArena 站点。它不是公网测试网站。" if zh else "192.168.5.2 is the host gateway inside the Colima VM. Browser containers use it to reach the WebArena sites running in the same Mac virtualization environment; it is not a public test website."}

{"执行拓扑" if zh else "Execution topology"}

{fallback}{topology}

{"隔离规则" if zh else "Isolation rules"}

1

{"一套固定端口站点环境,因此只允许一个方法运行。" if zh else "One fixed-port site stack permits only one running method."}

{task_count}

{"每种方法覆盖全部任务,方法开始前恢复站点;任务之间不重置。每题只保留一个最终逻辑结果,额外物理执行单独计数,输入未变且已经通过的任务不再执行。" if zh else "Each method covers every task after one site reset, with no reset between tasks. One final logical outcome is retained per task; extra physical calls are counted separately, and an unchanged task is not run again after passing."}

0

{"没有 Agent 决策和模型请求;Playwright 通过 CDP 重放冻结的 BrowserTool 动作契约。" if zh else "No Agent decisions or model calls; Playwright replays the frozen BrowserTool action contract over CDP."}

''' + environment_body = f'''

{"环境与评测流程" if zh else "Environment and evaluation flow"}

{"192.168.5.2 是 Colima 虚拟机中的宿主网关:浏览器容器通过它访问运行在同一台 Mac 虚拟化环境里的 WebArena 站点。它不是公网测试网站。" if zh else "192.168.5.2 is the host gateway inside the Colima VM. Browser containers use it to reach the WebArena sites running in the same Mac virtualization environment; it is not a public test website."}

{"执行拓扑" if zh else "Execution topology"}

{fallback}{topology}

{"隔离规则" if zh else "Isolation rules"}

1

{"一套固定端口站点环境,因此只允许一个方法运行。" if zh else "One fixed-port site stack permits only one running method."}

{task_count}

{"每种方法覆盖全部任务,方法开始前恢复站点;任务之间不重置。每题只保留一个最终逻辑结果,额外物理执行单独计数,输入未变且已经通过的任务不再执行。" if zh else "Each method covers every task after one site reset, with no reset between tasks. One final logical outcome is retained per task; extra physical calls are counted separately, and an unchanged task is not run again after passing."}

0

{_e(backend_note)}

''' environment_body = environment_body.replace("环境与评测流程", "环境与方法").replace("Environment and evaluation flow", "Environment and method") if "Rainbond" in str((profile.get("execution") or {}).get("site_stack") or ""): diagram_script = environment_body[environment_body.index('
{context.banner}{body}

{footer}

' + evidence_attr=' data-evidence-state="published_snapshot_score_audit"' if context.score_audit else '' + if context.score_audit:footer=link(base+'methodology/index.html','评测方法')+' · '+link(base+'changes/index.html','版本记录') + banner=context.banner.replace('href="audit/index.html"','href="'+base+'audit/index.html"') + page=f'{document_base}{esc(title)} · Web Fetch
{banner}{body}

{footer}

' target=contained_path(out,path);target.parent.mkdir(parents=True,exist_ok=True);target.write_text(page,encoding='utf-8') _RESEARCH_ROUTES=[ @@ -1582,6 +1637,8 @@ def failure_category(r,c): if r['metrics'].get('evaluation_excluded'):return '评测环境异常:统一排除比较' if not measured(r,c):return '无法判定:最终网页身份缺少证据' if r['metrics'].get('page_identity_required') and r['metrics'].get('page_identity_status')=='unavailable' else '未测:缺少验收证据' if succeeded(r,c):return '—' + if r['metrics'].get('page_identity_required') and r['metrics'].get('page_identity_status') != 'verified': + return '最终网页身份未核实' # Classification describes an already failed result; hints never veto success. metrics=r['metrics'] if (r.get('ok',False) and metrics.get('request_ok',True) @@ -1672,6 +1729,8 @@ def reason(r,c): if c.get('task_kind')=='structure': from .report_components import list_rule_description return f"检出 {r['metrics'].get('named_entry_count',0)} 个具名详情入口。"+list_rule_description(c) + if r['metrics'].get('page_identity_required') and r['metrics'].get('page_identity_status') != 'verified': + return '未通过:最终网页身份缺少证据,保留在计划任务分母中。' if c.get('task_kind')=='file':return '已返回原始文件,格式特征、长度及目标 SHA-256 校验通过。' if succeeded(r,c) else '没有返回可核对的原始文件;文字或重复输入地址不算文件。' if c.get('task_kind')=='resource': missing=[x['id'] for x in r['metrics'].get('resource_results',[]) if not x['pass']] @@ -1714,7 +1773,6 @@ def ranking(records,cases,track,base='',id=None,compact=False,audit=None,*,diagn if report_family(cases[r['case_id']])==track:groups[r['provider']].append(r) def sort_key(item): name,rs=item - if diagnostic:return name n=sum(measured(r,cases[r['case_id']]) for r in rs) return (-sum(succeeded(r,cases[r['case_id']]) for r in rs)/n if n else 1,name) rows=[];text_scopes={} @@ -1780,7 +1838,7 @@ def avg(key): directions={i:('quality_'+str(i),'max') for i in range(1,len(heads)) if heads[i] not in {'均值','P50','P95','判分完整性','审查结果'}} directions.update({i:('latency_'+str(i),'min') for i,h in enumerate(heads) if h in {'均值','P50','P95'}}) rows=comparative_rows(rows,directions) - result=table(heads,rows,id or 'ranking-'+track,True,'ranking',metrics=[] if compact else None,include_index=not diagnostic) + result=table(heads,rows,id or 'ranking-'+track,True,'ranking',metrics=[] if compact else None,include_index=not diagnostic,default_sort=(2+int(not diagnostic),'desc')) if text_scopes and len(set(text_scopes.values()))>1: result='
文字核对覆盖范围
    '+''.join('
  • '+esc(name)+f':{len(scope)}/{len(selected)} 例;'+esc('、'.join(selected[k]['name'] for k in sorted(scope)) or '无文字核对记录')+'
  • ' for name,scope in sorted(text_scopes.items()))+'
'+result if compact: @@ -1788,7 +1846,7 @@ def avg(key): if title_scope:result+=title_diagnostics(groups,selected,base,id or 'ranking-'+track) result+=matched_latency(groups,cases,base) from .report_components import task_pass_guide - return task_pass_guide(track,base,selected)+'

'+note+'

'+(result if diagnostic else result.replace(''+note+'

'+result def matched_latency(groups,cases,base): scopes=[{r['case_id'] for r in rows if succeeded(r,cases[r['case_id']])} for rows in groups.values()] @@ -1895,6 +1953,43 @@ def title_diagnostics(groups,selected,base,identifier): '

该诊断回答“已经返回的条目标题是否完整、干净”,不衡量榜单召回率、实时性或整页正文质量。

'+steps +table(['方法','标题完整度','标题纯净度','标题 F1','标题参考覆盖(可评分 / 返回)'],rows,identifier+'-titles',True,metrics=[])+ '') +def input_suite_comparison(dataset, cases, records): + """Compare the selected input groups declared by the frozen source inventory.""" + sources=dataset.get('comparison',{}).get('input_sources',[]) + if not sources or not any('case_ids' in source for source in sources):return '' + labels=dataset.get('comparison',{}).get('report_suite_labels',{}) + selected=set(cases);seen=set();groups=[] + for index,source in enumerate(sources,1): + ids=set(source.get('case_ids',[])) & selected + if seen & ids:raise ValueError('Input suite comparison contains overlapping tasks') + seen |= ids + if ids: + label=labels.get(source.get('path'),{}) + title=model_text(label.get('zh',f'输入组 {index}'),label.get('en',f'Input group {index}')) + groups.append((ids,title)) + if seen != selected:raise ValueError('Input suite comparison omits selected tasks') + providers=sorted({row['provider'] for row in records});rows=[] + for provider in providers: + values=cell(link('providers/'+provider+'.html',provider_label(provider))) + for ids,_ in groups: + subset=[row for row in records if row['provider']==provider and row['case_id'] in ids] + if {row['case_id'] for row in subset} != ids or len(subset)!=len(ids): + raise ValueError('Input suite comparison requires one result per method and task') + available=[row for row in subset if measured(row,cases[row['case_id']])] + passed=sum(succeeded(row,cases[row['case_id']]) for row in available) + value=f'{passed}/{len(available)} · '+rate(passed,len(available)) + if len(available)!=len(ids):value+=' · '+model_text(f'{len(ids)-len(available)} 题未评分',f'{len(ids)-len(available)} unscored tasks') + values+=cell(value,passed/len(available) if available else -1) + rows.append(('',values)) + directions={index+1:(f'input-suite-{index+1}','max') for index in range(len(groups))} + rows=comparative_rows(rows,directions) + return section(model_text('按输入组比较','Comparison by input group'), + '

'+model_text('按冻结输入清单分组;分母是本轮选中的可评分任务。这些样本用于检查特定场景,不代表随机网页分布。', + 'Groups follow the frozen input inventory; denominators count selected, scored tasks. These samples test particular scenarios and do not represent randomly sampled webpages.')+'

'+ + table([model_text('方法','Method')]+[title+f' · n={len(ids)}' for ids,title in groups],rows,'input-suite-comparison',metrics=[]), + 'input-suite-comparison-section') + + def overall_ranking(records,cases,audit=None,*,diagnostic=False,call_budget_seconds=None): excluded={r['case_id'] for r in records if r['metrics'].get('evaluation_excluded')} records=[r for r in records if not r['metrics'].get('evaluation_excluded')] @@ -1916,8 +2011,8 @@ def overall_ranking(records,cases,audit=None,*,diagnostic=False,call_budget_seco values+=cell(ms(percentile(times,.5)),percentile(times,.5)) values+=cell(ms(percentile(times,.95)),percentile(times,.95)) if audit:values+=review_cell(audit,rs) - rows.append((passed,name,('',values))) - rows=[row for _,_,row in sorted(rows,key=lambda x:x[1] if diagnostic else (-x[0],x[1]))] + rows.append((passed/n if n else -1,name,('',values))) + rows=[row for _,_,row in sorted(rows,key=lambda x:(-x[0],x[1]))] tracks=active_tracks(cases) heads=['方法','请求成功率','综合任务通过率']+(['无法判定'] if has_unknown else [])+[label for _,label in tracks]+['总耗时均值','总耗时 P50','总耗时 P95'] if audit:heads+=['审查结果'] @@ -1945,8 +2040,11 @@ def overall_ranking(records,cases,audit=None,*,diagnostic=False,call_budget_seco if diagnostic: intro='

本地未认证诊断结果,仅展示保存的任务判定与耗时,不用于产品排名。通过不代表返回内容干净;请同时查看全文指标和挑战提示。

' help=help.replace('总榜指标说明','诊断指标说明') - rendered=table(heads,rows,'ranking-overall',True,'ranking',metrics=[],include_index=not diagnostic) - return intro+timing_note+help+(rendered if diagnostic else rendered.replace('

{label}

{coverage}'+ranking(records,cases,track,compact=True,audit=audit,diagnostic=diagnostic)+f'

{link("datasets/index.html#track-"+track,"案例与参考 →")}

')) overall_title=('任务判定汇总(非排名)' if diagnostic else model_text('本轮任务通过率排名','Task pass-rate ranking for this sample') if local_pass_rate_ranking(dataset) else '综合通过率总榜') + if score_audit:overall_title='任务通过情况' budgets={m['timeout_seconds'] for m in manifests if m.get('timeout_seconds') is not None} home+=section(overall_title,overall_ranking(records,cases,audit,diagnostic=diagnostic,call_budget_seconds=next(iter(budgets)) if len(budgets)==1 else None),'overall-ranking') - if diagnostic: + home+=input_suite_comparison(dataset,cases,records) + if diagnostic and not score_audit: home+=result_ledger_section(out,records) low=[r for r in records if not r['metrics'].get('evaluation_excluded') and succeeded(r,cases[r['case_id']]) and cases[r['case_id']].get('ground_truth_main_content') and r['metrics'].get('word_precision') is not None and r['metrics']['word_precision']<.3] home+=section('通过结果的内容诊断',f'

{len(low)} 条通过结果的参考词精确率低于 30%。此比例用于定位检查对象,不是统一失败线;通过不保证干净正文,正文参考的范围和时间也会影响该指标。

','coverage-quality') @@ -2150,7 +2254,7 @@ def generate(out:Path,dataset:dict,records:list,manifests:list,*,include_english home+=section('全部方法共同成功任务的耗时',overview_matched_latency(records,cases),'overall-matched-latency') if (out/'resource-cohort/resource-comparison.json').is_file(): home+=section('CPU、内存与网络',overview_resources(out),'overall-resources') - home+=section('按任务类别查看诊断结果' if diagnostic else f'{track_label} 类分榜',('

方法按名称排列,不提供名次。任务判定、内容质量和耗时分别展示。

' if diagnostic else '

分榜用于判断具体能力。默认按任务通过率排序,点击列名可切换;质量分数与通过率分别计算。

')+tabs('home-rankings',panels),'results') + home+=section('按任务类别查看结果' if score_audit else '按任务类别查看诊断结果' if diagnostic else f'{track_label} 类分榜',('

任务通过率、内容质量和耗时分别展示。

' if score_audit else '

默认按任务通过率降序排列,不提供名次。任务判定、内容质量和耗时分别展示。

' if diagnostic else '

分榜用于判断具体能力。默认按任务通过率排序,点击列名可切换;质量分数与通过率分别计算。

')+tabs('home-rankings',panels),'results') write(out,'index.html','评测报告',home+'') controls=filter_controls([('track','类型',tracks),('category','类别',[(x,x) for x in dict.fromkeys(c['category'] for c in cases.values())])]) rows=[] @@ -2404,6 +2508,11 @@ def generate(out:Path,dataset:dict,records:list,manifests:list,*,include_english if answer_view and answer_view['original_sha256']!=answer_view['published_sha256']: output_note=(f'{len(output):,} 字符 · '+link('../'+answer_view['path'],'下载公开副本','Download public view')+' · '+link('../'+answer_view['receipt'],'查看脱敏记录','View redaction record')+ '
'+model_text('目录或敏感值已隐藏;成绩来自评分原件。原件与公开副本的校验值见运行字段。','Paths or sensitive values are hidden; scores come from the original. Original/public hashes are listed in the run fields.')) + if r.get('scoring',{}).get('mode')=='published_snapshot_rescore': + view=r['scoring']['artifact_view'] + view_label={'original':'与原哈希相同的保存输出','recovered_original':'按原哈希恢复的输出(本页展示公开副本)','published_projection':'公开脱敏副本'}[view] + output_note=f'{len(output):,} 字符 · 本次评分输入:'+esc(view_label)+' · '+link('../'+r['artifact_path'],'下载公开输出') + output_note+='
原始调用输出 SHA-256:'+esc(r['scoring']['original_content_sha256'])+'
本次评分输入 SHA-256:'+esc(r['scoring']['scored_content_sha256'])+'' if readable_output(output)!=output:output_note+=' · 控制字节在页面中显示为 \\xNN' actual_body=(corruption+f'
{esc(readable_output(output))}
' if output else f'
{esc(empty_output_reason(r,c,output))}
') @@ -2487,6 +2596,8 @@ def generate(out:Path,dataset:dict,records:list,manifests:list,*,include_english comparison=section('方法组合对比','

按同一任务逐项比较,不用各方法不同的成功样本拼接结论。成功与失败均沿用主榜的程序判定;点击方法可查看标准答案、实际返回和原因。

'+method_comparisons(cases,bycase,names),'method-comparison') write(out,'analysis/index.html','结果分析',hero('结果分析','先比较方法在同一任务上的成败组合,再筛选全部执行记录。')+comparison+section('全部执行记录',controls+table(['方法']+headers,[(case_attrs(r,cases[r['case_id']]),cell(link('../providers/'+r['provider']+'.html',provider_label(r['provider']),browser_method_label(r['provider'],language='en')))+attempt_row(r,cases[r['case_id']])[1]) for r in records],'filtered-table',cls='all-method-results'))) write_support(out,records,manifests,authored_interpretation=authored_interpretation) + if score_audit: + render_score_audit(out,score_audit,score_binding) from .report_changes import render_changes if records and all(row.get('provider_type') == 'fixture' for row in records): write(out,'changes/index.html','示例说明',hero('合成示例','本报告由本地测试适配器生成。没有正式评测发布记录。')) @@ -2795,6 +2906,22 @@ def write_support(out,records,manifests,*,authored_interpretation=True): methodology+='

'+model_text( '正文验收使用原生接口交付的内容,不以额外解析器补齐回答。原始文件任务按其字节验收规则核对;请求、输出与错误保留在逐次调用证据中。', 'Text acceptance uses content delivered by the native interface, without an additional parser completing the answer. Original-file tasks use their byte-acceptance rules; requests, outputs and errors remain in per-call evidence.')+'

' + from .report_components import render_acceptance_scope + if dataset.get('cases'): + methodology+=render_acceptance_scope({case['id']:case for case in dataset['cases']},records) + score_binding=dataset.get('comparison',{}).get('score_audit') + if score_binding: + methodology+=section('数据范围','

'+esc(f'本报告使用原有 {score_binding["total_attempts"]} 条调用记录和耗时,没有新增浏览器调用。其中 {score_binding["projected_scoring_inputs"]} 条评分输入为公开脱敏副本,{score_binding["recovered_originals"]} 条为按原哈希恢复的输出。逐项核对范围为 {score_binding["reviewed_attempts"]}/{score_binding["total_attempts"]} 条判定变化。')+'

'+link('../audit/index.html','输入差异与逐题验收记录')+'

') + input_rows=[] + for name in sorted({row['provider'] for row in records}): + selected=[row for row in records if row['provider']==name] + counts=Counter(row.get('scoring',{}).get('artifact_view','original') for row in selected) + input_rows.append(('',cell(esc(provider_label(name)))+cell(str(counts['original']))+cell(str(counts['recovered_original']))+cell(str(counts['published_projection']))+cell(str(len(selected))))) + methodology+=section('评分输入来源',table(['方法','原始输出','按哈希恢复','公开脱敏副本','合计'],input_rows,metrics=[])) + revision=dataset.get('definition_revision') + if revision: + methodology+=section('计分定义','

数据集:'+esc(dataset['id'])+'

定义版本:'+link('../'+revision['path'],revision['id'])+' · SHA-256:'+esc(revision['sha256'])+'

原始定义数据集:'+esc(revision['parent_dataset_id'])+' · SHA-256:'+esc(revision['parent_dataset_sha256'])+'

') + write(out,'methodology/index.html','评测方法',methodology) downloads=' · '.join(link('../'+path,label) for path,label in [('dataset.json','评测集与参考'),('results.jsonl','全部调用记录'),('summary.json','分类汇总'),('execution.json','执行批次')]+([('implementation-provenance.json','浏览器版本与二进制来源')] if (out/'implementation-provenance.json').is_file() else [])+([('reference-revisions.json','参考修订收据')] if (out/'reference-revisions.json').is_file() else [])+([('scoring-revisions.json','评分修订收据')] if (out/'scoring-revisions.json').is_file() else [])+([('latency-corrections.json','时延纠正收据')] if (out/'latency-corrections.json').is_file() else [])+([('request-status-corrections.json','请求状态纠正收据')] if (out/'request-status-corrections.json').is_file() else [])) reproduce='

在评测仓库中执行;读取 evaluation/ 的当前实测数据,不调用供应商接口。

make setup\nmake report EVIDENCE=/absolute/path/to/evidence\nmake report-check

输出目录:reports/webfetch/。固定的 Python 3.11 与 uv.lock 依赖不满足时会立即失败。凭据仅从本地环境加载,不包含在报告中。

' @@ -2803,8 +2930,8 @@ def write_support(out,records,manifests,*,authored_interpretation=True): profile_path=dataset.get('comparison',{}).get('reproduction_profile') if profile_path: candidate=Path(profile_path) - if candidate.is_absolute() or not candidate.parts or '..' in candidate.parts or candidate.parts[0]!='evaluation' or candidate.name!='current-report.json': - raise ValueError('Report reproduction profile must stay under evaluation/') + if candidate.is_absolute() or not candidate.parts or '..' in candidate.parts or candidate.suffix!='.json': + raise ValueError('Report reproduction profile must be a relative JSON path without parent traversal') destination=dataset['comparison'].get('report_output','reports/webfetch') command=f'uv run --frozen python scripts/build_report.py --profile {profile_path} --evidence /absolute/path/to/evidence --output {destination}\nuv run --frozen python scripts/check_report.py {destination} --profile {profile_path}' reproduce='

从本轮独立配置和冻结证据构建,不重新调用浏览器。

'+esc(command)+'

重新测量使用仓库的认证入口;既定题目和验收规则在调用前冻结,只有明确声明需要实时参考的任务才做前后采集。

' @@ -2820,7 +2947,7 @@ def write_support(out,records,manifests,*,authored_interpretation=True): provider_rows=[] for name in sorted({r['provider'] for r in records}): subset=[r for r in records if r['provider']==name] - versions=sorted({str(value) for r in subset if (value:=method_version(r,provenance))}) + versions=method_versions(subset,provenance) identities=[p.get('implementation_identity',{}) for m in manifests for p in m.get('providers',[]) if p.get('name')==name] versions=versions or sorted({str(identity['reported_version']) for identity in identities if identity.get('reported_version')}) versions=versions or sorted({str(identity['repository']['head']) for identity in identities if identity.get('repository',{}).get('head')}) @@ -2892,6 +3019,7 @@ def option_text(value): support+=section('核心复现指令',reproduce) write(out,'reproduce/index.html','环境与复现',support) write(out,'accounts/index.html','账号与安装',render_account_requirements(records)) + from .report_methods import render_methods write(out,'adoption/index.html','方法介绍',render_methods(out,records,hero=hero,section=section,table=table,cell=cell,link=link,esc=esc,authored=authored_interpretation)) @@ -3020,66 +3148,51 @@ def _wmb_matched_latency(records,names,labels,english=False,prefix='../'): rows=[] for name in names: rows.append(('',cell(link(prefix+'providers/'+name+'.html',labels[name]))+''.join(_wmb_timing_cell(data['matched'],name,key) for key in ('mean','p50','p95')))) - return '

'+h(f'同题耗时对照:共同成功的 {len(common)} 页',f'Matched-page timing: {len(common)} pages with success across all methods')+'

'+h(f'以下每行使用完全相同的 {len(common)} 页,只取最终成功 fetch 的单次耗时。它用于检查同页速度差异;共同成功页是在运行后确定的,所以不据此给整个 200 页评测排速度名次。',f'Every row uses the same {len(common)} pages and the duration of the final successful fetch only. This diagnoses same-page timing differences. The shared-success subset is determined after execution, so it does not rank speed over the entire 200-page benchmark.')+'

'+_wmb_timing_note(english,matched=True)+'

'+table([h('方法','Method'),h('均值耗时','Mean latency'),h('P50 耗时','P50 latency'),h('P95 耗时','P95 latency')],rows,cls='wmb-matched-latency-table',metrics=[])+'
'+h(f'未进入同题对照的 {len(excluded)} 页(仍保留在完整结果中)',f'{len(excluded)} pages outside this comparison (retained in the full matrix)')+'
    '+''.join('
  • '+link(prefix+'datasets/'+case_id+'.html',case_id)+'
  • ' for case_id in excluded)+'
' + return '

'+h(f'同题耗时对照:共同成功的 {len(common)} 页',f'Matched-page timing: {len(common)} pages with success across all methods')+'

'+h(f'以下每行使用完全相同的 {len(common)} 页,只取最终成功 fetch 的单次耗时。它用于检查同页速度差异;共同成功页是在运行后确定的,所以不据此给完整任务清单排速度名次。',f'Every row uses the same {len(common)} pages and the duration of the final successful fetch only. This diagnoses same-page timing differences. The shared-success subset is determined after execution, so it does not rank speed over the entire declared benchmark.')+'

'+_wmb_timing_note(english,matched=True)+'

'+table([h('方法','Method'),h('均值耗时','Mean latency'),h('P50 耗时','P50 latency'),h('P95 耗时','P95 latency')],rows,cls='wmb-matched-latency-table',metrics=[])+'
'+h(f'未进入同题对照的 {len(excluded)} 页(仍保留在完整结果中)',f'{len(excluded)} pages outside this comparison (retained in the full matrix)')+'
    '+''.join('
  • '+link(prefix+'datasets/'+case_id+'.html',case_id)+'
  • ' for case_id in excluded)+'
' def _wmb_latency_rerun(diagnostic,english=False,prefix='',formal_round=None): + """Render recorded crossover summaries without assuming versions or cohort size.""" h=lambda zh,en:en if english else zh if not diagnostic:return '' - runs=diagnostic['balanced_200']['runs'];tail=diagnostic['tail_repeat'];resources=diagnostic['resource'] - tail_count=len(tail['cases']) - tail_outliers=[row for row in tail['cases'] - if abs(row['older_median_ms']-row['newer_median_ms'])>20] - tail_newer_faster=sum(row['newer_median_ms']' if value' + runs=diagnostic['balanced_200']['runs'] + rows=[] + for version,run in runs.items(): + values=[] + for key in ('mean','p50','p95'): + comparable=[other[key] for other in runs.values()] + value=run[key] + won=value==min(comparable) and comparable.count(value)==1 + content=''+ms(value)+'' if won else ms(value) + values.append(cell(content,value).replace('' if formal_round is not None: - body+=f'

'+h('这组较早的复测与另一项浏览器任务同时运行,只用于定位慢页和调用顺序效应;首页正式耗时采用之后无并发认证复测的预选第二轮,因此两张表的数值不同。', - 'This earlier crossover overlapped another browser benchmark and is retained only to diagnose slow pages and call order. The formal home-page timing uses the preselected second round of a later certified run without concurrent benchmarks, so the table values differ.')+'

' + body+=f'

'+h(f'本表保留较早的诊断数据。正式主页耗时来自另一份认证收据预先选定的第 {formal_round} 轮;两者的调用和环境条件分别绑定,不混为同一次测量。',f'This table retains an earlier diagnostic. Formal home-page timing comes from preselected round {formal_round} of a separate certified receipt. Their calls and environment conditions are bound separately and are not one measurement.')+'

' body+=table([h('版本','Version'),h('成功调用 / 全部调用','Successful / all calls'),h('均值耗时','Mean latency'),h('P50 耗时','P50 latency'),h('P95 耗时','P95 latency')],rows,cls='wmb-rerun-table',metrics=[]) - body+='

'+h('绿色标出同页交叉复测中该列较低的耗时。','Green marks the lower latency in this matched crossover.')+'

' - balanced=diagnostic['balanced_200'] - body+='

'+h(f'最初单批测试出现的 1.1.7 耗时劣势,在交叉顺序复测中没有复现。连续两次请求同一页时,先请求的一次平均多花 {balanced["first_call_extra_mean_ms"]:.0f} ms({balanced["first_call_slower_count"]}/{balanced["paired_success_calls"]} 对更慢);两版都受影响。单次调用仍会偶发变慢,不能保证每一页每一次都耗时相同。', - f'The apparent 1.1.7 slowdown from the initial separate batches did not recur in the balanced crossover. When the same page was fetched twice in succession, the first call took {balanced["first_call_extra_mean_ms"]:.0f} ms longer on average ({balanced["first_call_slower_count"]}/{balanced["paired_success_calls"]} pairs slower); both versions were affected. Individual calls still have slow outliers, so equal latency on every call cannot be guaranteed.')+'

' - repetitions={row['repetitions_per_version'] for row in tail['cases']} - if len(repetitions)!=1 or tail['within_20_ms']+len(tail_outliers)!=tail_count: + tail=diagnostic['tail_repeat'];cases=tail['cases'] + outliers=[row for row in cases if abs(row['older_median_ms']-row['newer_median_ms'])>20] + if tail['within_20_ms']+len(outliers)!=len(cases): raise ValueError('Latency tail summary disagrees with the per-page evidence') - repetition=repetitions.pop() - tail_calls=tail_count*2*repetition - tail_direction=(f'' - +h(f'其余页面中新版更快 {tail_newer_faster} 页、旧版更快 {tail_older_faster} 页。', - f'Among the rest, 1.1.7 was faster on {tail_newer_faster} pages and 1.1.5 on {tail_older_faster}.')+'') - body+='

'+h(f'针对最初差距最大的 {tail_count} 页,两版每页再各测 {repetition} 次,共 {tail_calls} 次均成功,且同一版本的正文哈希保持一致。{tail["within_20_ms"]}/{tail_count} 页的两版中位耗时相差不超过 20 ms;', - f'For the {tail_count} pages with the largest initial gaps, each version was called {repetition} more times per page: all {tail_calls} calls succeeded, and output hashes stayed constant within each version. Median timing differed by no more than 20 ms on {tail["within_20_ms"]}/{tail_count} pages; ')+tail_direction+h('下表逐页列出典型耗时和最慢一次,便于区分稳定差异与偶发慢调用。',' The table shows each page’s typical and slowest call.')+'

' - body+='
'+h('查看差异最大的 20 页及逐页复测','Inspect all 20 divergent pages and repeated calls')+''+table([h('页面','Page'),h('1.1.5 中位耗时','1.1.5 median'),h('1.1.7 中位耗时','1.1.7 median'),h('1.1.5 最慢一次','1.1.5 slowest'),h('1.1.7 最慢一次','1.1.7 slowest')],case_rows,cls='wmb-rerun-cases',metrics=[])+'
' - failure=diagnostic['failure_retest'] - body+='

'+h('两版各有 1 页未成功,均为同一页。各自独立重测时,首次和一次重试仍因页面脚本跳转原站而失败;禁用页面脚本后两版都返回正文。继续按相同条件重试不能恢复该页;禁用脚本会改变评测条件,因此该页仍计为失败,覆盖率为 199/200。', - 'Both versions failed on the same page. In separate retests, each initial call and retry again failed after the page script navigated to the origin site. Disabling page-authored JavaScript returned body text for both versions. Repeating the same condition did not recover the page; disabling the script changes the test, so this page remains failed and coverage is 199/200.')+' '+link(prefix+'datasets/'+failure['case_id']+'.html',h('查看失败页','Inspect the failed page'))+'

' - body+=table([h('本轮','Rerun'),h('系统采样','System samples'),h('最低空闲 CPU','Minimum idle CPU'),h('最低未用内存','Minimum unused memory'),h('新增 swap-out','New swap-outs')],resource_rows,cls='wmb-rerun-resource',metrics=[]) - body+='

'+h('下表是此前单批运行期间的宿主机采样,不能代替交叉复测时的逐调用资源测量,也不能证明负载导致慢调用。', - 'The resource samples below come from the preceding separate batches. They are not per-call measurements from the crossover and do not prove host load caused slow calls.')+' '+link(('../' if english else '')+prefix+'latency-diagnostic.json',h('下载逐页复测与证据绑定','Download per-page repeats and evidence bindings'))+'

' + if cases: + body+='
'+h(f'查看 {len(cases)} 页的逐页复测',f'Inspect repeated calls for {len(cases)} pages')+'' + body+=table([h('页面','Page'),h('旧版中位耗时','Earlier-version median'),h('新版中位耗时','Later-version median'),h('旧版最慢一次','Earlier-version maximum'),h('新版最慢一次','Later-version maximum')],[('',cell(link(prefix+'datasets/'+row['case_id']+'.html',row['case_id']))+cell(ms(row['older_median_ms']),row['older_median_ms'])+cell(ms(row['newer_median_ms']),row['newer_median_ms'])+cell(ms(row['older_max_ms']),row['older_max_ms'])+cell(ms(row['newer_max_ms']),row['newer_max_ms'])) for row in cases],cls='wmb-rerun-cases',metrics=[]) + body+='
' + resource_rows=[] + for name,resource in diagnostic['resource'].items(): + resource_rows.append(('',cell(esc(name))+cell(str(resource['samples']))+cell(f"{resource['min_idle_cpu_pct']:.1f}%")+cell(f"{resource['min_unused_memory_mb']:.0f} MB")+cell(str(resource['swapouts_delta'])))) + newer_faster=sum(row['newer_median_ms']'+h(f'复测 {len(cases)} 页;中位耗时差超过 20 ms 的页面中,新版更快 {newer_faster} 页,旧版更快 {older_faster} 页。',f'{len(cases)} pages were repeated; among pages differing by more than 20 ms in median latency, the later version was faster on {newer_faster} and the earlier version on {older_faster}.')+'

' + failure=diagnostic.get('failure_retest') or {} + if failure.get('case_id'): + body+='

'+h(f"故障复测记录每版 {failure.get('attempts_per_version','—')} 次调用;正式失败结果保留。",f"Failure retests record {failure.get('attempts_per_version','—')} calls per version; formal failed results remain unchanged.")+' '+link(prefix+'datasets/'+failure['case_id']+'.html',h('查看失败页','Inspect the failed page'))+'

' + if failure.get('script_disabled_returns_body'): + body+='

'+h('禁用页面脚本的诊断调用返回了正文。这改变了执行条件,不替换正式调用的结果。','Disabling page-authored JavaScript returned body text in the diagnostic calls. This changed the execution condition and does not replace formal outcomes.')+'

' + body+=table([h('记录的运行','Recorded run'),h('系统采样','System samples'),h('最低空闲 CPU','Minimum idle CPU'),h('最低未用内存','Minimum unused memory'),h('新增 swap-out','New swap-outs')],resource_rows,cls='wmb-rerun-resource',metrics=[]) + body+='

'+h('宿主采样不能单独证明负载导致慢调用。逐次响应、失败复测与证据绑定见原始收据。', + 'Host samples alone do not establish that load caused slow calls. Responses, failure retests and evidence bindings remain in the source receipt.')+' '+link(('../' if english else '')+prefix+'latency-diagnostic.json',h('下载复测证据','Download repeated-call evidence'))+'

' return '
'+body+'
' @@ -3287,7 +3400,7 @@ def pills(selected,kind): values=cell(link('../datasets/'+case['id']+'.html',case['id']))+cell(esc(_wmb_category(case,'language'))+' · '+esc(_wmb_category(case,'level')))+cell(pills(delivered,'pass'))+cell(pills(failed,'fail')) attrs=' data-delivered="'+esc(' '.join(delivered))+'" data-failed="'+esc(' '.join(failed))+'"'+_wmb_row_attrs(case) rows.append((attrs,values)) - # All 200 frozen pages remain in the DOM; filtering is client-side and evidence links stay per call. + # All selected frozen pages remain in the DOM; filtering is client-side and evidence links stay per call. result='

'+h('同一页是否生成正文','Body text on the same page')+'

'+h('这里只比较截止时间内是否得到非空 Markdown 正文,不代表内容质量通过;打开方法可看该页分数、人工真值和实际输出。','This only compares whether non-empty Markdown body text was produced by the deadline; it is not a content-quality pass. Open a method for scores, the human reference and actual output.')+'

'+controls+'
'+''.join(f'{values}' for i,(attrs,values) in enumerate(rows))+'
'+h('序号','No.')+'Track ID'+h('分类','Category')+''+h('生成正文的方法','Methods with body text')+''+h('没有正文的方法','Methods without body text')+'
{i+1}
' return result @@ -3351,9 +3464,10 @@ def dialog(target,title,body): return '
'+body+''.join(dialogs)+'
' if failed else '
'+body+''.join(dialogs)+'
' +@with_measured_method_labels def generate_webmainbench(out:Path,dataset:dict,records:list,summaries:dict,manifests:list,review:dict|None=None,research:dict|None=None,corrections:dict|None=None,adoption_data:dict|None=None,fixture_correction:dict|None=None,teds_revision:tuple|None=None,anomaly_review:dict|None=None,latency_diagnostic:dict|None=None,*,include_english:bool=False,report_content:dict|None=None): """Render the current WebMainBench report with the established report shell.""" - from .report_changes import ROOT, render_webmainbench_history, webmainbench_history + from .report_changes import render_webmainbench_history out.mkdir(parents=True,exist_ok=True);(out/'assets').mkdir(exist_ok=True) (out/'assets/style.css').write_text(STYLE+EXTRA,encoding='utf-8') (out/'assets/report.js').write_text(JS,encoding='utf-8') @@ -3370,8 +3484,10 @@ def generate_webmainbench(out:Path,dataset:dict,records:list,summaries:dict,mani names=list(dataset['coverage_plan']['static_html']) failure_types=sorted({r['_output_error'] for r in records if not r['_body_present'] and r['_output_error']}) failed_request_count=sum(not r['_body_present'] for r in records) - case_count=len(cases);result_count=case_count*len(names) - labels={'kitesurf':'Kitesurf','lightpanda':'Lightpanda','obscura':'Obscura','moli':'Moli','moli-1.1.7':'Moli 1.1.7','chromium':'Chromium 152'} + case_count=len(cases);method_count=len(names);result_count=case_count*method_count + labels={name: browser_method_registry().get(name,{}).get('label') or (native_browser_engine(name) or name).capitalize() for name in names} + def scope(text): + return text.replace('{case_count}',str(case_count)).replace('{method_count}',str(len(names))).replace('{result_count}',str(result_count)) metric_order=['overall','text_edit','code_edit','formula_edit','table_edit','table_TEDS'] corrected=teds_revision is not None display_score=_wmb_pct @@ -3407,38 +3523,38 @@ def core_rows(english=False): rows.append(('',values)) return rows for english in ((False,True) if include_english else (False,)): - h=lambda zh,en:en if english else zh + h=lambda zh,en:scope(en if english else zh) corrected_label=h('修正后复算','recomputed after correction') corrected_suffix=(f' ({corrected_label})' if english else f'({corrected_label})') metric_labels={name:name+(corrected_suffix if corrected and name in ('overall','table_TEDS') else '') for name in metric_order} status=(h(f'{case_count} 页 × {len(names)} 方法 = {result_count:,} 条结果;证据与算术核验通过,表格结构评分的有效性尚未通过。',f'{case_count} pages × {len(names)} methods = {result_count:,} results; evidence and arithmetic checks passed, but table-structure score validity did not.') if review and review.get('status')=='passed' else h('测量完成;独立审查尚未通过,当前页面不可发布。','Measurement complete; independent review has not passed, so this build is not publishable.')) if corrected: status=h(f'{case_count} 页 × {len(names)} 种方法;官方仓库和榜单保持原样。报告用独立复算器修复已证实的 TEDS 计算缺陷,再处理同一批保存输出。',f'{case_count} pages × {len(names)} methods. The official repository and leaderboard remain unchanged. An independent recomputation corrects the demonstrated TEDS defect on the same saved outputs.') - home=hero(h(f'WebMainBench-545 · 预选 200 页 · {len(names)} 种方法实测',f'WebMainBench-545 · preselected 200 · {len(names)} measured methods'),h(f'{len(names)} 种方法读取同一批冻结 HTML,并与同一份人工校准 Markdown 比较;逐页响应、空正文和原因放在结果详情中。',f'{len(names)} methods read the same frozen HTML and are compared with the same human-calibrated Markdown; page-level responses, empty body text and causes live in result details.')) + home=hero(h(f'WebMainBench-545 · 预选 {case_count} 页 · {len(names)} 种方法实测',f'WebMainBench-545 · preselected {case_count} · {len(names)} measured methods'),h(f'{len(names)} 种方法读取同一批冻结 HTML,并与同一份人工校准 Markdown 比较;逐页响应、空正文和原因放在结果详情中。',f'{len(names)} methods read the same frozen HTML and are compared with the same human-calibrated Markdown; page-level responses, empty body text and causes live in result details.')) home+='
'+esc(status)+'
' home+=section(h('先看这张榜在测什么','What this ranking measures'),'
'+h('输入:同一份网页存档','Input: identical archived HTML')+'

'+h(f'{case_count} 页 × {len(names)} 种方法;不访问实时源站。',f'{case_count} pages × {len(names)} methods; no live origin fetch.')+'

'+h('目标:主内容,而非整页所有文字','Target: main content, not every page element')+'

'+h('正文、代码、公式、表格;导航、广告和推荐链接不属于目标。','Body text, code, formulas and tables; navigation, ads and recommendations are outside the target.')+'

'+h('分数:与人工主内容答案有多接近','Score: similarity to the human main-content reference')+'

'+h('多带页面模板会扣分;表格文字保留但结构丢失,也会扣分。低分不等于网页没有获取到。','Extra boilerplate and lost table structure reduce scores. A low score does not mean the page was not retrieved.')+'

'+link('guide/index.html#content',h('看真实页面怎样被扣分 →','See a real scored page →'))+' · '+link('methodology/index.html#input-routes',h(f'{len(names)} 种方法怎样读取同一份 HTML →',f'How all {len(names)} methods read the same HTML →'))+'

'+h('这不是实时抓取成功率或浏览器综合能力排名。','This is not a live-fetch success rate or an overall browser-capability ranking.')+'

','benchmark-scope') core_headers=[h('方法','Method'),metric_labels['overall'] if corrected else h('overall(待校验)','overall (under review)'),'text_edit','code_edit','formula_edit','table_edit',metric_labels['table_TEDS'] if corrected else h('table_TEDS(待校验)','table_TEDS (under review)'),h('成功页 / 总页数','Successful / all pages'),h('均值耗时','Mean latency'),h('P50 耗时','P50 latency'),h('P95 耗时','P95 latency')] core_note=_wmb_timing_note(english) - core_note+=h(' TEDS 和 overall 为修正后复算值。',' TEDS and overall are recomputed after correction.') if corrected else h(' 原始 TEDS 存在已证实的计算缺陷;它和 overall 不标领先者。',' Original TEDS has a proven calculation defect; it and overall have no highlighted leader.') - core_note+=' '+(h(' Moli 两版耗时采用同一批 200 页交叉复测的第二轮;旧耗时与完整纠正收据保留供核对。其余四种方法仍来自各自原批次。',' Moli timing uses round two of the same 200-page crossover; prior timing and the full correction receipt remain available. The other four methods retain their original separate batches.') if latency_corrected else h(' Moli 两版来自独立耗时批次,不能凭此表断定 1.1.7 更慢;下方同页交叉复测轮换了调用顺序。',' The Moli versions were timed in separate batches; this table does not establish that 1.1.7 is slower. The matched crossover below alternates call order.')) - core_note+=' '+link('review/index.html',h('原始分与修正依据 →','Original scores and correction evidence →')) + core_note+=' '+h(f'耗时修订使用预先选定的第 {formal_round} 轮;逐次调用与修订收据保留供核对。',f'Timing corrections use preselected round {formal_round}; physical calls and correction receipts remain available.') if latency_corrected else '' + core_note+=' '+h(f'耗时修订使用预先选定的第 {formal_round} 轮;逐次调用与修订收据保留供核对。',f'Timing corrections use preselected round {formal_round}; physical calls and correction receipts remain available.') if latency_corrected else '' + core_note+=' '+h(f'耗时修订使用预先选定的第 {formal_round} 轮;逐次调用与修订收据保留供核对。',f'Timing corrections use preselected round {formal_round}; physical calls and correction receipts remain available.') if latency_corrected else '' timing_key='
'+h('质量分','Quality scores')+' '+h('按每项下方的计分页数求平均','Averaged over the pages shown under each component')+''+h('均值耗时 · P50 · P95','Mean latency · P50 · P95')+' '+h('仅最终成功 fetch;不加前次失败时间,成功页数单列','Final successful fetch only; previous failures are not added, and the success count is shown separately')+'
' coverage_note='' if latency_diagnostic: - failed_case=latency_diagnostic['failure_retest']['case_id'] - coverage_note='

'+h('成功页数反映浏览器读取本地测试服务器所提供冻结网页的结果。一页自带脚本跳转原站,Moli 两版重试后仍被隔离网络阻断;禁用脚本能返回正文,但改变了测试条件。该页的失败保留,不能把 199/200 解释成版本固有能力或实时抓取成功率。','Success counts describe browsers reading frozen pages from the local test server. One page script navigates to its live origin; both Moli versions still fail after a retry under network isolation. Disabling the script returns body text but changes the test condition. The failure stays in the results; 199/200 is neither an inherent version-capability claim nor a live-fetch success rate.')+' '+link('datasets/'+failed_case+'.html',h('查看该页证据','Inspect this page'))+'

' + coverage_note='

'+h('成功页数与失败详情对应本轮选中的冻结页面,不代表实时网站抓取能力。','Delivered-page counts and failure details describe the selected frozen pages, not live-site retrieval ability.')+'

' + coverage_note=coverage_note.replace('

',' '+link('datasets/'+latency_diagnostic['failure_retest']['case_id']+'.html',h('查看失败页证据','Inspect the failed-page evidence'))+'

') home+=section(h('核心结果','Core results'),timing_key+'

'+core_note+'

'+table(core_headers,core_rows(english),'wmb-core-results',True,cls='wmb-core-table',metrics=[],default_sort=(2,'desc'))+coverage_note+_wmb_table_population(records,names,labels,english,prefix='')+'
'+h('公式计分示例与计算流程','Formula-scoring example and flow')+''+_wmb_denominator_example(records,english,case_count)+'
'+f'

{link("analysis/index.html",h("筛选逐页结果与响应 →","Filter page-level results and responses →"))} · {link("methodology/index.html",h("完整评分规则 →","Full scoring rules →"))} · {link("review/index.html",h("官方原始分与纠正记录 →","Original scores and corrections →"))}

') home+=section(h('相同页面上的耗时','Timing on identical pages'),_wmb_matched_latency(records,names,labels,english,prefix=''),'matched-latency') if latency_diagnostic: - home+=section(h('Moli 版本耗时比较','Moli version latency comparison'),_wmb_latency_rerun(latency_diagnostic,english,formal_round=formal_round),'moli-latency-rerun') + home+=section(h('版本耗时比较','Version latency comparison'),_wmb_latency_rerun(latency_diagnostic,english,formal_round=formal_round),'moli-latency-rerun') _wmb_write(out,'index.html',h('评测报告','Evaluation report'),home,english) dataset_rows=[] for c in cases.values(): meta=c['meta'] dataset_rows.append((_wmb_row_attrs(c),cell(link(c['id']+'.html',c['id']))+cell(esc(meta.get('language') or 'unavailable'))+cell(esc(meta.get('level')))+cell(esc('+'.join(meta.get('table') or []) or 'none'))+cell(esc('+'.join(meta.get('code') or []) or 'none'))+cell(esc('+'.join(meta.get('equation') or []) or 'none')))) - body=hero(h('评测集','Dataset'),h('WebMainBench-545 母集里预先冻结的 200 页;筛选发生在任何方法结果产生之前。','A frozen 200-page sample from the WebMainBench-545 parent, selected before any method result was produced.')) - body+=section(h('200 页案例','200 pages'),'

'+h('可按语言、难度和内容类型查找页面。打开 Track ID 可查看冻结 HTML、人工校准的 Markdown 参考文本和官方元数据;方法成绩在结果分析中单独查看。官方评分直接比较转换结果与参考文本,没有预先写好的逐题得分清单。','Filter by language, difficulty and content type. Open a track ID for the frozen HTML, human-calibrated Markdown reference and official metadata; method results are in the analysis section. Official scoring compares each conversion with the reference directly, without a prewritten per-task checklist.')+'

'+_wmb_controls(cases,labels,english,include_method=False,include_metric=False,include_delivery=False)+_wmb_filter_table([h('Track ID','Track ID'),h('语言','Language'),h('难度','Difficulty'),h('表格','Table'),h('代码','Code'),h('公式','Formula')],dataset_rows,'wmb-cases',english)) + body=hero(h('评测集','Dataset'),h('WebMainBench 母集里预先冻结的 {case_count} 页;筛选发生在任何方法结果产生之前。','A frozen {case_count}-page sample from the WebMainBench parent, selected before any method result was produced.')) + body+=section(h('{case_count} 页案例','{case_count} pages'),'

'+h('可按语言、难度和内容类型查找页面。打开 Track ID 可查看冻结 HTML、人工校准的 Markdown 参考文本和官方元数据;方法成绩在结果分析中单独查看。官方评分直接比较转换结果与参考文本,没有预先写好的逐题得分清单。','Filter by language, difficulty and content type. Open a track ID for the frozen HTML, human-calibrated Markdown reference and official metadata; method results are in the analysis section. Official scoring compares each conversion with the reference directly, without a prewritten per-task checklist.')+'

'+_wmb_controls(cases,labels,english,include_method=False,include_metric=False,include_delivery=False)+_wmb_filter_table([h('Track ID','Track ID'),h('语言','Language'),h('难度','Difficulty'),h('表格','Table'),h('代码','Code'),h('公式','Formula')],dataset_rows,'wmb-cases',english)) sample=dataset.get('sample',{});sampling=sample.get('sampling',{}) if sampling: distribution_rows=[] @@ -3451,8 +3567,8 @@ def core_rows(english=False): distribution_rows.append((f' data-axis="{esc(axis)}" data-parent="{parent_count}" data-sample="{sample_count}"',cell(esc(category))+cell(str(parent_count))+cell(str(sample_count)))) manifest_href='../sample-manifest.json' if not english else '../../sample-manifest.json' distribution_controls='

' - distribution_table=table([h('分类','Category'),h('母集 545','Parent 545'),h('样本 200','Sample 200')],distribution_rows,'wmb-distribution',metrics=[]) - body+=section(h('这 200 页怎么选出来的','How these 200 pages were selected'),'

'+h('语言、难度、表格、代码和公式是五套彼此独立的分类,同一页会同时属于每套分类。选择任一分类轴后,“样本 200”这一列会合计为 200;不能把五套分类混在一起相加。完整名单在运行任何浏览器之前已经确定,没有按分数挑页。','Language, difficulty, table, code and formula are five independent classification axes; every page belongs to one category on each axis. After selecting an axis, the “Sample 200” column sums to 200. Counts across different axes must not be added together. The manifest was fixed before any browser ran; scores did not affect selection.')+'

'+h('固定种子:','Fixed seed: ')+''+esc(sampling.get('seed',''))+' · '+link(manifest_href,h('下载 200 页名单','Download the 200-page list'))+'

'+distribution_controls+distribution_table) + distribution_table=table([h('分类','Category'),h('母集','Parent'),h('样本 {case_count}','Sample {case_count}')],distribution_rows,'wmb-distribution',metrics=[]) + body+=section(h('这 {case_count} 页怎么选出来的','How these {case_count} pages were selected'),'

'+h('语言、难度、表格、代码和公式是五套彼此独立的分类,同一页会同时属于每套分类。选择任一分类轴后,“样本 {case_count}”这一列会合计为 {case_count};不能把五套分类混在一起相加。完整名单在运行任何浏览器之前已经确定,没有按分数挑页。','Language, difficulty, table, code and formula are five independent classification axes; every page belongs to one category on each axis. After selecting an axis, the “Sample {case_count}” column sums to {case_count}. Counts across different axes must not be added together. The manifest was fixed before any browser ran; scores did not affect selection.')+'

'+h('固定种子:','Fixed seed: ')+''+esc(sampling.get('seed',''))+' · '+link(manifest_href,h('下载 {case_count} 页名单','Download the {case_count}-page list'))+'

'+distribution_controls+distribution_table) if research: def evidence_links(urls): values=[] @@ -3464,7 +3580,7 @@ def evidence_links(urls): for item in research.get('direct_benchmarks',[]): sources=evidence_links(item.get('sources',[])) selection.append(('',cell(esc(item['name']))+cell(esc(item.get('scope' if english else 'scope_zh','—')))+cell(esc(item.get('metric' if english else 'metric_zh','—')))+cell(esc(';'.join(item.get('users',[])) or '—'))+cell(esc(item.get('disposition' if english else 'disposition_zh','—')))+cell(sources))) - body+=section(h('为什么选择 WebMainBench-545','Why WebMainBench-545'),'

'+esc(research['decision']['summary_en' if english else 'summary_zh'])+'

'+esc(research['decision']['qualification_en' if english else 'qualification_zh'])+'

'+table([h('候选','Candidate'),h('范围','Scope'),h('指标','Metric'),h('采用者 / 使用者','Named users'),h('结论','Decision'),h('证据','Evidence')],selection,cls='wide-prose-table',metrics=[])) + body+=section(h('为什么选择 WebMainBench','Why WebMainBench'),'

'+esc(research['decision']['summary_en' if english else 'summary_zh'])+'

'+esc(research['decision']['qualification_en' if english else 'qualification_zh'])+'

'+table([h('候选','Candidate'),h('范围','Scope'),h('指标','Metric'),h('采用者 / 使用者','Named users'),h('结论','Decision'),h('证据','Evidence')],selection,cls='wide-prose-table',metrics=[])) wcxb=next((item for item in research.get('direct_benchmarks',[]) if item.get('id')=='wcxb'),None) if wcxb: limits=wcxb.get('limitations' if english else 'limitations_zh',[]) @@ -3476,38 +3592,38 @@ def evidence_links(urls): _wmb_write(out,'datasets/index.html',h('评测集','Dataset'),body,english) method=hero(h('评测方法','Methodology'),h('从一页冻结网页开始,看浏览器交付了什么、官方怎样给分,以及哪些分数现在不能用。','Start with one frozen page: what the browser delivered, how the official scorer calculates it, and which figures cannot currently support conclusions.')) - flow_source=(r''' + flow_source=scope(r''' flowchart LR - A["1 冻结 HTML
同一批 200 页"] --> B["2 六种方法转换
每页得到最终响应"] + A["1 冻结 HTML
同一批 {case_count} 页"] --> B["2 {method_count} 种方法转换
每页得到最终响应"] B --> C{"3 最终调用
是否成功"} C -->|"失败"| D["结果详情
错误类型 · 响应 · 原因"] C -->|"成功"| E["4 与人工 Markdown 比较
文本 · 代码 · 公式 · 表格"] E --> F["5 逐页分数
两侧都没有该类内容:跳过
只有一侧有:计 0 分"] - F --> G["6 分项均值
分数下方显示计入页数 / 200"] + F --> G["6 分项均值
分数下方显示计入页数 / {case_count}"] E --> H["排除空内容与错误页
最终成功 fetch:均值 · P50 · P95"]''' if not english else r'''flowchart LR - A["1 Frozen HTML
the same 200 pages"] --> B["2 Six methods convert
one final response per page"] + A["1 Frozen HTML
the same {case_count} pages"] --> B["2 {method_count} methods convert
one final response per page"] B --> C{"3 Did the final
call succeed?"} C -->|"No"| D["Result detail
error type · response · cause"] C -->|"Yes"| E["4 Compare with human Markdown
text · code · formula · table"] E --> F["5 Per-page components
absent on both sides: skipped
present on one side: scored zero"] - F --> G["6 Component means
scored pages / 200 shown below score"] + F --> G["6 Component means
scored pages / {case_count} shown below score"] E --> H["Exclude empty content and error pages
final successful fetch: mean · P50 · P95"]''') flow_fallback='
'+''.join(f'{index} {label}' for index,label in enumerate(([h('冻结输入','Frozen input'),h('方法转换','Conversion'),h('检查响应与正文','Inspect response and body'),h('逐页评分','Per-page scoring'),h('按计入页数求均值','Mean over scored pages')]),1))+'
' flow='
'+flow_fallback+'
' method+=section(h('一页怎样变成一行结果','How one page becomes one result'),'

'+h('先看整条路径,再按需展开下面的实现细节。正文是否生成、接口是否响应和为什么为空,属于逐页结果;质量分只回答已生成内容与人工答案有多接近。','Start with the full path, then open implementation details only when needed. Whether body text exists, whether the interface responded and why it is empty belong to the page result; quality scores only describe similarity to the human reference.')+'

'+flow) - input_text=h('每个方法接收预选 200 页官方 html 字段的同一组 UTF-8 字节。四款本地方法从 OS 网络隔离的 loopback HTTPS 读取原样正文,每批使用专用临时 CA;HTTPS 避免冻结页因协议差异触发伪跳转。它们的原始批次覆盖母集 545 页,报告只投影到预先冻结的 200-ID 清单,不按结果选题。Kitesurf 从头只执行这 200 页,使用 Cloudflare 官方 html 输入并拒绝外部资源类型。冻结 HTML 自带的内联脚本可以执行,但非 loopback 子资源和数据请求不能出网;原 URL 只保留为来源元数据。每个方法内部严格串行;不同方法的批次可重叠,因此耗时是本轮客户端观测,不是隔离硬件微基准。',"Every method receives the same UTF-8 bytes from the official html field for the preselected 200 pages. Four local methods read the unchanged body over loopback HTTPS under an OS network sandbox, using a dedicated ephemeral CA per batch; HTTPS prevents false redirects caused only by a scheme mismatch. Their source batches measured all 545 parent pages, while this report projects only the pre-frozen 200-ID manifest and never selects by outcome. Kitesurf runs these 200 pages from scratch through Cloudflare's documented html input with external resource types rejected. Inline scripts contained in the frozen HTML may run, but non-loopback subresource and data requests cannot reach the network; the original URL is evidence metadata only. Calls are serial within each method; provider batches may overlap, so latency is this run's client observation rather than an isolated hardware microbenchmark.") - cue_text=h('数据 HTML 含公开标注属性 cc-select;六种通用配置均未按该属性定制,但这是数据集固有的潜在线索。','The dataset HTML contains public cc-select annotation attributes. None of the six generic configurations is customized to use them, but they remain an inherent benchmark cue.') - method+=section(h('输入与运行边界','Input and runtime boundary'),'
'+h('Kitesurf · Cloudflare 接口','Kitesurf · Cloudflare API')+''+h('冻结 HTML → API html 字段 → Markdown','Frozen HTML → API html field → Markdown')+''+h('直接提交同一份 HTML 字节,不让远端重新抓原网址。','The same HTML bytes are submitted directly; the remote service does not refetch the source URL.')+'
Lightpanda · Moli · Obscura · Chromium'+h('冻结 HTML → 本地 HTTPS → 浏览器 → Markdown','Frozen HTML → local HTTPS → browser → Markdown')+''+h('浏览器打开本地地址,服务器返回同一份 HTML。','The browser opens a local URL whose server returns the same HTML.')+'

'+h('每页有一份冻结 HTML 和一份人工校准 Markdown。六种方法转换同一份 HTML;原始 URL 只记录来源,本轮不访问实时网站。','Each page has frozen HTML and a human-calibrated Markdown reference. All six methods convert the same HTML; the original URL records provenance and is not fetched live in this run.')+'

'+h('查看网络隔离、本地 HTTPS 服务和批次细节','Network isolation, local HTTPS server and batch details')+'

'+input_text+'

'+cue_text+'

'+link('../datasets/index.html',h('选一页查看完整输入和答案','Open a page with full input and reference'))+'

','input-routes') + input_text=h('每个方法接收所选冻结页面的同一组 HTML 字节。提交接口、隔离条件和实际物理调用见执行证据;不从历史批次推断本轮范围。','Every method receives the same selected frozen HTML bytes. Submission interfaces, isolation controls and physical calls follow the execution evidence; the current scope is not inferred from historical batches.') + cue_text=h('数据 HTML 含公开标注属性 cc-select;{method_count} 种通用配置均未按该属性定制,但这是数据集固有的潜在线索。','The dataset HTML contains public cc-select annotation attributes. None of the {method_count} generic configurations is customized to use them, but they remain an inherent benchmark cue.') + method+=section(h('输入与运行边界','Input and runtime boundary'),'
'+h('Kitesurf · Cloudflare 接口','Kitesurf · Cloudflare API')+''+h('冻结 HTML → API html 字段 → Markdown','Frozen HTML → API html field → Markdown')+''+h('直接提交同一份 HTML 字节,不让远端重新抓原网址。','The same HTML bytes are submitted directly; the remote service does not refetch the source URL.')+'
Lightpanda · Moli · Obscura · Chromium'+h('冻结 HTML → 本地 HTTPS → 浏览器 → Markdown','Frozen HTML → local HTTPS → browser → Markdown')+''+h('浏览器打开本地地址,服务器返回同一份 HTML。','The browser opens a local URL whose server returns the same HTML.')+'

'+h('每页有一份冻结 HTML 和一份人工校准 Markdown。{method_count} 种方法转换同一份 HTML;原始 URL 只记录来源,本轮不访问实时网站。','Each page has frozen HTML and a human-calibrated Markdown reference. All {method_count} methods convert the same HTML; the original URL records provenance and is not fetched live in this run.')+'

'+h('查看网络隔离、本地 HTTPS 服务和批次细节','Network isolation, local HTTPS server and batch details')+'

'+input_text+'

'+cue_text+'

'+link('../datasets/index.html',h('选一页查看完整输入和答案','Open a page with full input and reference'))+'

','input-routes') method+=section(h('表格怎样进入评分','How tables enter scoring'),_wmb_table_example(english),'table-scoring') formulas=[('text_edit',h('完整 Markdown 与人工答案','Full Markdown against the human reference'),h('字符编辑距离 ÷ 两份文本中较长者的长度;导航、页脚等多余内容也会拉低分数。','Character edit distance divided by the longer text; extra navigation and footer content can lower the score.')),('code_edit',h('从两份 Markdown 找出的代码块','Code blocks found in the two Markdown files'),h('只比较提取出的代码字符,按较长代码长度归一化。','Compare extracted code characters, normalized by the longer code length.')),('formula_edit',h('从两份 Markdown 找出的公式','Formulas found in the two Markdown files'),h('只比较提取出的公式字符,按较长公式长度归一化。','Compare extracted formula characters, normalized by the longer formula length.')),('table_edit',h('从两份 Markdown 找出的表格内容','Tables found in the two Markdown files'),h('比较规范化后的表格字符;普通段落里有表格字样,不等于输出了可识别的表格。','Compare normalized table characters; table words in a paragraph do not form a recognized table.')),('table_TEDS',h('Markdown 表格还原出的“表格—行—单元格”结构','The table–row–cell structure reconstructed from Markdown'),h('先把 Markdown 表格还原成表格、每一行和每个单元格,再计算把输出结构改成人工答案结构需要多少次增、删、改。只有单元格文字、没有可识别的 Markdown 表格语法时,无法还原表格结构。','Reconstruct tables, rows and cells from Markdown, then count the insertions, deletions and changes needed to turn the output structure into the reference structure. Cell text without recognizable Markdown table syntax cannot be reconstructed as a table.'))] method+=section(h('每项分数怎么来','How component scores are produced'),'

'+h('先从实际输出和人工答案里分别识别代码、公式和表格;text_edit 则保留整份 Markdown。字符项的分数=1 − 编辑距离 ÷ 较长文本长度,完全相同为 100%,只剩一侧内容时就是 0%。表格结构项按下文记录的计算缺陷修正后复算。','The scorer first extracts code, formulas and tables from the output and reference; text_edit instead keeps the entire Markdown. For character metrics, score = 1 − edit distance / longer length: identical content scores 100%, while content on only one side scores 0%. The table-structure component is recomputed after correcting the calculation defect documented below.')+'

'+table([h('指标','Metric'),h('比较对象','Compared content'),h('计算与读法','Calculation and reading')],[('',cell(k)+cell(subject)+cell(explanation)) for k,subject,explanation in formulas],cls='wide-prose-table',metrics=[])+f'

{h("使用官方提交 9d991bd 的确定性 use_llm=false 配置。代码、公式或表格在两侧都没有时,官方编辑距离返回失败,该项退出均值;只有一侧有内容时,编辑距离正常计算为 0 分并进入均值。","The pinned official commit is 9d991bd with deterministic use_llm=false. When code, formulas or tables are absent on both sides, the official edit-distance metric returns a failed result and the component is excluded from the mean. When content exists on only one side, edit distance succeeds with a zero score and the page enters the mean.")}

') denominator_rows=[('',cell(h('两侧都没有','Absent on both sides'))+cell(h('不计分,不进入该项均值','No score; outside the component mean'))),('',cell(h('只在人工答案中出现','Reference only'))+cell(h('0 分,进入均值','Zero score; included'))),('',cell(h('只在方法输出中出现','Output only'))+cell(h('0 分,进入均值','Zero score; included'))),('',cell(h('两侧都有','Present on both sides'))+cell(h('计算相似度,进入均值','Similarity score; included')))] - method+=section(h('哪些页面进入每项平均','Which pages enter each component mean'),'

'+h('每种方法固定处理 200 页。最终调用失败时不评分;调用成功时,评分器分别从方法输出和人工答案识别该项内容,即使输出后来被识别为错误页,也保留实际计算的分数;“未计算”不等于 0 分。若评分器本身执行失败,该页也不进入该项均值。','Each method receives 200 fixed pages. Failed final calls are not scored. Successful calls are scored from output and reference content; calculated scores remain even when the output is later classified as an error page. “Not calculated” is different from zero. A metric execution error also excludes the page from that component mean.')+'

'+table([h('该项内容状态','Component content'),h('官方处理','Official treatment')],denominator_rows,metrics=[])+f'

{link("../index.html#denominator-example",h("看 Moli 公式分的实际 200 页分流","See the actual 200-page Moli formula split"))}

','denominator-rules') + method+=section(h('哪些页面进入每项平均','Which pages enter each component mean'),'

'+h('每种方法固定处理 {case_count} 页。最终调用失败时不评分;调用成功时,评分器分别从方法输出和人工答案识别该项内容,即使输出后来被识别为错误页,也保留实际计算的分数;“未计算”不等于 0 分。若评分器本身执行失败,该页也不进入该项均值。','Each method receives {case_count} fixed pages. Failed final calls are not scored. Successful calls are scored from output and reference content; calculated scores remain even when the output is later classified as an error page. “Not calculated” is different from zero. A metric execution error also excludes the page from that component mean.')+'

'+table([h('该项内容状态','Component content'),h('官方处理','Official treatment')],denominator_rows,metrics=[])+f'

{link("../index.html#denominator-example",h("看 Moli 公式分的实际 {case_count} 页分流","See the actual {case_count}-page Moli formula split"))}

','denominator-rules') method+=section(h('表格结构分为什么要复算','Why the table-structure score is recomputed'),'

'+h('官方固定版本先把“表格—行—单元格”结构写成一段括号字符串,再把字符串直接交给 APTED。APTED 在这里需要的是能逐层读取子项的树;收到字符串后,它没有读到真实的行和单元格。实测一页的输出只有 5 个结构项,人工答案有 570 个,官方记录的编辑距离却小于 1,因而给出 99.8% 的 TEDS。仅把相差的 565 个结构项补齐,代价就不可能小于 565。','The pinned official version first serializes the table–row–cell structure into one bracketed string and then passes the string directly to APTED. APTED needs a tree whose children can be traversed; with the string it does not see the real rows and cells. In one measured page, the output has 5 structural items and the reference has 570, yet the official recorded edit distance is below 1 and the TEDS score is 99.8%. Adding the 565 missing structural items alone cannot cost less than 565 edits.')+'

'+h('本报告没有改动官方仓库或官方榜单。我们在独立复算器中把同一张表按“表格—行—单元格”交给 APTED,沿用官方的 Markdown 解析、文字处理和增删改代价。浏览器输出、计分页数和耗时都没有变化;官方原始分与修正后复算分并列保留。table_edit 仍比较规范化后的表格 HTML 字符串。','This report does not modify the official repository or leaderboard. An independent recomputation passes the same table to APTED as an explicit table–row–cell tree while retaining the official Markdown parsing, text handling and edit costs. Browser outputs, scored-page counts and latency are unchanged; original official scores and recomputed values are both retained. Table_edit still compares normalized table HTML strings.')+'

'+link('../attempts/obscura__949e1468-eed2-43ab-96e6-5dcb07c52d4b__r001.html',h('查看 5 对 570 个结构项的完整答案和输出','Inspect the complete 5-versus-570-item case'))+' · '+link('https://github.com/opendatalab/WebMainBench/blob/9d991bdc00c57b57521499494d96be85c31317ba/webmainbench/metrics/teds_metrics.py',h('官方 TEDS 源码','Official TEDS source'))+'

') method+=section(h('聚合、计时和不可比较项','Aggregation, timing and non-comparable fields'),'

'+h('每项聚合分=该项成功计算的逐页分数之和 ÷ 实际计入页数;榜单 overall=五项聚合分的算术平均。逐页 overall 则平均该页成功计算的项目,因此它不能直接拿来替代榜单 overall。逐页显示“未计算”只有两类原因:代码、公式或表格在参考答案和实际输出中都不存在,或者评分器执行失败;详情页会显示该页属于哪一类。','Each component aggregate is the sum of successfully calculated page scores divided by its actual scored-page count; leaderboard overall is the arithmetic mean of the five aggregates. Per-page overall averages only that page’s calculated components, so it cannot replace leaderboard overall. A page-level component is marked “not calculated” for exactly two reasons: the content type is absent from both the reference and output, or the scorer failed. The result detail identifies which reason applies.')+'

'+h('成功 fetch 耗时只取最终成功调用,从请求开始到转换响应结束,前次失败不相加。全部调用的累计时间另存为执行成本。浏览器批次各自串行,但不同方法可能重叠;Cloudflare 远端时间和本地 CLI 时间处于不同执行环境。是否生成正文、质量分和耗时分开报告,不能混成一个分数。','Successful-fetch time is the final successful call from request start to converted response, excluding earlier failures. Accumulated time across all calls is retained separately as execution cost. Calls are serial within a browser batch, but method batches may overlap; Cloudflare remote time and local CLI time come from different execution environments. Body-text outcome, quality and latency are reported separately, not folded into one score.')+'

','calculation') cost_rows=[('',cell(labels[name])+cell(str(case_count))+''.join(cell(ms(timing['cost'][name][key])) for key in ('mean','p50','p95'))) for name in names] - method+=section(h('成功耗时、同题诊断与执行成本','Successful timing, matched diagnosis and execution cost'),'

'+h('主表只使用最终成功且有目标正文的 fetch,取 elapsed_ms;成功页数与均值、P50、P95 同时展示。错误页、空内容和最终调用失败均排除。一次机会还是重试不改变最终结果的身份,也不把前次耗时加到成功 fetch 上。','The primary table uses elapsed_ms from the final successful fetch with target content, alongside the success count, mean, P50 and P95. Error pages, empty content and failed final calls are excluded. A retry does not create a separate logical result or add the previous call’s time to the successful fetch.')+'

'+h('同题诊断取六种配置都成功的页面交集,每种方法用完全相同的页面。这个运行后确定的子集不代表全部 200 页,因此不用于速度排名,也不删除其余页面。每页的原始调用链和累计执行成本保留,便于复核,但不混入成功 fetch 耗时。','The matched diagnosis uses the intersection of successful pages across all six configurations. Every method uses identical pages. This post-execution subset is not a speed ranking for all 200 pages and does not remove any other results. Original call chains and accumulated execution cost remain available for audit, separately from successful-fetch latency.')+'

'+h('原口径:全部 200 页的累计执行成本(非成功 fetch 速度)','Previous measure: accumulated execution cost on all 200 pages (not successful-fetch speed)')+'

'+h('这里保留 total_elapsed_ms:每页所有物理调用耗时相加,再对固定 200 页求均值、P50、P95。它描述这轮执行花费的总时间。','This retains total_elapsed_ms: sum all physical-call durations per page, then calculate mean, P50 and P95 over the fixed 200 pages. It describes execution cost for this run.')+'

'+table([h('方法','Method'),h('页数','Pages'),h('均值','Mean'),'P50','P95'],cost_rows,metrics=[])+'
','latency-policy') + method+=section(h('成功耗时、同题诊断与执行成本','Successful timing, matched diagnosis and execution cost'),'

'+h('主表只使用最终成功且有目标正文的 fetch,取 elapsed_ms;成功页数与均值、P50、P95 同时展示。错误页、空内容和最终调用失败均排除。一次机会还是重试不改变最终结果的身份,也不把前次耗时加到成功 fetch 上。','The primary table uses elapsed_ms from the final successful fetch with target content, alongside the success count, mean, P50 and P95. Error pages, empty content and failed final calls are excluded. A retry does not create a separate logical result or add the previous call’s time to the successful fetch.')+'

'+h('同题诊断取{method_count} 种配置都成功的页面交集,每种方法用完全相同的页面。这个运行后确定的子集不代表全部 {case_count} 页,因此不用于速度排名,也不删除其余页面。每页的原始调用链和累计执行成本保留,便于复核,但不混入成功 fetch 耗时。','The matched diagnosis uses the intersection of successful pages across all {method_count} configurations. Every method uses identical pages. This post-execution subset is not a speed ranking for all {case_count} pages and does not remove any other results. Original call chains and accumulated execution cost remain available for audit, separately from successful-fetch latency.')+'

'+h('原口径:全部 {case_count} 页的累计执行成本(非成功 fetch 速度)','Previous measure: accumulated execution cost on all {case_count} pages (not successful-fetch speed)')+'

'+h('这里保留 total_elapsed_ms:每页所有物理调用耗时相加,再对固定 {case_count} 页求均值、P50、P95。它描述这轮执行花费的总时间。','This retains total_elapsed_ms: sum all physical-call durations per page, then calculate mean, P50 and P95 over the fixed {case_count} pages. It describes execution cost for this run.')+'

'+table([h('方法','Method'),h('页数','Pages'),h('均值','Mean'),'P50','P95'],cost_rows,metrics=[])+'
','latency-policy') _wmb_write(out,'methodology/index.html',h('评测方法','Methodology'),method,english) analysis=hero(h('结果分析','Analysis'),h('先比较同一页上的方法差异,再按分类查看分数,最后下钻到每次响应、正文和原因。','Compare methods on the same page, then inspect category scores, and finally drill into each response, body text and cause.')) @@ -3547,7 +3663,7 @@ def evidence_links(urls): vals+=''.join(_wmb_metric_cell(r,metric) for metric in metric_order[1:]) vals+=cell(ms(r['elapsed_ms']),r['elapsed_ms'])+cell(link(target,h('逐项证据 →','Metric evidence →'))) inspect_rows.append((_wmb_row_attrs(case,r),vals)) - analysis+=section(h('逐页结果与响应','Page results and responses'),'

'+h(f'完整矩阵共有 200×6 条结果,默认先显示 {failed_request_count} 条没有生成正文的转换。每行直接说明是否得到正文,以及接口或程序最后留下的是正文、错误信息、错误页还是无可读响应。点击 Track ID 或方法名进入详情,核对完整响应、人工答案、逐项评分和目前能证实的原因。',f'The complete matrix contains 200×6 results and initially shows the {failed_request_count} conversions without body text. Each row states whether body text was produced and whether the final API/worker material is body text, an error response, an error page or no readable response. Open the track ID or method for the complete response, human reference, component scores and the currently supported cause.')+'

'+_wmb_controls(cases,labels,english,default_request='failed',failure_types=failure_types)+'

'+h('表格可横向滚动查看五项分数和耗时;Track ID 与方法名都可直接进入详情。','Scroll horizontally for component scores and latency; both the track ID and method name open the detail.')+'

'+_wmb_filter_table([h('Track ID','Track ID'),h('方法','Method'),h('语言','Language'),h('难度','Difficulty'),h('表格','Table'),h('代码','Code'),h('公式','Formula'),h('是否生成正文','Body text produced'),h('响应与正文','Response and body')]+[metric_labels[m] for m in metric_order[1:]]+[h('最终 fetch 耗时','Final fetch latency'),h('详情','Detail')],inspect_rows,'wmb-attempt-matrix',english)) + analysis+=section(h('逐页结果与响应','Page results and responses'),'

'+h(f'完整矩阵共有 {case_count}×{method_count} 条结果,默认先显示 {failed_request_count} 条没有生成正文的转换。每行直接说明是否得到正文,以及接口或程序最后留下的是正文、错误信息、错误页还是无可读响应。点击 Track ID 或方法名进入详情,核对完整响应、人工答案、逐项评分和目前能证实的原因。',f'The complete matrix contains {case_count}×{method_count} results and initially shows the {failed_request_count} conversions without body text. Each row states whether body text was produced and whether the final API/worker material is body text, an error response, an error page or no readable response. Open the track ID or method for the complete response, human reference, component scores and the currently supported cause.')+'

'+_wmb_controls(cases,labels,english,default_request='failed',failure_types=failure_types)+'

'+h('表格可横向滚动查看五项分数和耗时;Track ID 与方法名都可直接进入详情。','Scroll horizontally for component scores and latency; both the track ID and method name open the detail.')+'

'+_wmb_filter_table([h('Track ID','Track ID'),h('方法','Method'),h('语言','Language'),h('难度','Difficulty'),h('表格','Table'),h('代码','Code'),h('公式','Formula'),h('是否生成正文','Body text produced'),h('响应与正文','Response and body')]+[metric_labels[m] for m in metric_order[1:]]+[h('最终 fetch 耗时','Final fetch latency'),h('详情','Detail')],inspect_rows,'wmb-attempt-matrix',english)) _wmb_write(out,'analysis/index.html',h('结果分析','Analysis'),analysis+analysis_tail,english) review_body=hero(h('核验记录','Review'),h('保留官方原始结果,记录计算修正的范围和影响;逐页原因见结果详情。','Original official results are retained alongside the scope and effects of corrections; page-level causes are in result details.')) @@ -3561,7 +3677,7 @@ def evidence_links(urls): review_body+=section(h('TEDS 计算修正','TEDS calculation correction'),'

'+h(f'同一批 {revision_review["logical_results"]:,} 条结果全部重新计算,{revision_review["changed_scores"]} 条 TEDS 分数变化。没有重新调用浏览器,输入、原始输出、计分页数和耗时均未改变。本报告没有修改官方仓库或官方榜单;表中的“修正后”是报告侧独立复算值。',f'All {revision_review["logical_results"]:,} results were reprocessed; {revision_review["changed_scores"]} TEDS values changed. No browser was called again. Inputs, raw outputs, scored-page counts and latency are unchanged. This report does not modify the official repository or leaderboard; “corrected” values are independently recomputed by this report.')+'

'+table([h('方法','Method'),h('原始 TEDS','Original TEDS'),h('修正 TEDS','Corrected TEDS'),h('原始 overall','Original overall'),h('修正 overall','Corrected overall'),h('TEDS 计分页数','Pages scored for TEDS')],effects,'wmb-correction-effects',metrics=[])+ '

'+link('../methodology/index.html',h('树节点错误及修正原理','Tree-node defect and correction'))+' · '+link(raw_prefix+'teds-correction.json',h('纠正范围与源码校验值','Correction scope and source hashes'))+' · '+link(raw_prefix+'teds-correction-review.json',h('完整性与算术核验','Integrity and arithmetic audit'))+' · '+link(raw_prefix+'original-results.jsonl',h('官方原始逐页结果','Original official page results'))+'

') if review: - review_body+=section(h('原始结果证据检查','Original-result evidence checks'),'

'+h('原始核验检查了 1,200 条结果的覆盖范围、文件哈希、调用耗时、文本类评分及分项聚合。它不能单独证明评分算法正确,也不能证明每条失败都已找到根因。TEDS 的纠正及重新核验单独记录在上方。','The original audit checked the 1,200-result scope, file hashes, call latency, text metrics and component aggregation. It does not alone establish algorithm validity or the root cause of every failure. The TEDS correction and its follow-up checks are recorded above.')+'

'+link(raw_prefix+'result-review.json',h('下载原始核验记录','Download the original audit'))+'

') + review_body+=section(h('原始结果证据检查','Original-result evidence checks'),'

'+h('原始核验检查了 {result_count} 条结果的覆盖范围、文件哈希、调用耗时、文本类评分及分项聚合。它不能单独证明评分算法正确,也不能证明每条失败都已找到根因。TEDS 的纠正及重新核验单独记录在上方。','The original audit checked the {result_count}-result scope, file hashes, call latency, text metrics and component aggregation. It does not alone establish algorithm validity or the root cause of every failure. The TEDS correction and its follow-up checks are recorded above.')+'

'+link(raw_prefix+'result-review.json',h('下载原始核验记录','Download the original audit'))+'

') else: review_body+=section(h('原始核验缺失','Original audit missing'),'

'+h('没有原始核验文件,不能发布。','The original audit is missing; publication is blocked.')+'

') error_outputs=sum(1 for r in records if _wmb_error_page(contained_path(out,r['artifact_path']).read_text(encoding='utf-8',errors='replace'))) @@ -3572,7 +3688,7 @@ def evidence_links(urls): anomaly_rows.append(('',cell(link('../datasets/'+finding['case_id']+'.html',finding['case_id']))+cell(esc(providers))+cell(esc(finding['observed_en' if english else 'observed_zh']))+cell(esc(finding['cause_en' if english else 'cause_zh'])))) scope=anomaly_review['scope'] anomaly_href='../../webmainbench-anomaly-review.json' if english else '../webmainbench-anomaly-review.json' - review_body+=section(h('异常结果根因复核','Root-cause review of anomalous results'),'

'+h(f'自动规则从 1,200 条结果中触发 {scope["signal_triggered_results"]} 条,实际集中在 {scope["distinct_cases"]} 个页面。复核读取正式输出、人工答案、程序错误和同页六方法差异,并用不计分的删减对照定位触发条件。所有触发项均已归入下列原因;对照输入不是原始任务,因此没有替换调用、分数、分母或耗时。',f'Deterministic rules triggered {scope["signal_triggered_results"]} of 1,200 results, concentrated in {scope["distinct_cases"]} pages. Review used the authoritative outputs, human references, program errors and same-page six-method differences, then non-scoring ablation controls to isolate triggers. Every triggered result maps to a cause below. Controls are not the original task, so no call, score, denominator or latency was replaced.')+'

'+table([h('页面','Page'),h('受影响方法','Affected methods'),h('对照观察','Control observation'),h('根因与证据边界','Cause and evidence boundary')],anomaly_rows,cls='wide-prose-table',metrics=[])+f'

{link(anomaly_href,h("下载绑定哈希的复核记录","Download the hash-bound review record"))}

') + review_body+=section(h('异常结果根因复核','Root-cause review of anomalous results'),'

'+h(f'自动规则从 {result_count} 条结果中触发 {scope["signal_triggered_results"]} 条,实际集中在 {scope["distinct_cases"]} 个页面。复核读取正式输出、人工答案、程序错误和同页六方法差异,并用不计分的删减对照定位触发条件。所有触发项均已归入下列原因;对照输入不是原始任务,因此没有替换调用、分数、分母或耗时。',f'Deterministic rules triggered {scope["signal_triggered_results"]} of {result_count} results, concentrated in {scope["distinct_cases"]} pages. Review used the authoritative outputs, human references, program errors and same-page six-method differences, then non-scoring ablation controls to isolate triggers. Every triggered result maps to a cause below. Controls are not the original task, so no call, score, denominator or latency was replaced.')+'

'+table([h('页面','Page'),h('受影响方法','Affected methods'),h('对照观察','Control observation'),h('根因与证据边界','Cause and evidence boundary')],anomaly_rows,cls='wide-prose-table',metrics=[])+f'

{link(anomaly_href,h("下载绑定哈希的复核记录","Download the hash-bound review record"))}

') review_body+=section(h('核验边界','Review boundary'),'

'+h(f'正式结果中有 {failed_request_count} 次转换没有目标正文,其中 {error_outputs} 条留下的是浏览器错误页;它们现在都能从逐页详情进入对应的直接证据和已验证原因。异常复核覆盖确定性规则触发的 36 条结果,不等于对其余正常结果逐条做人工作品质量复审。服务未公开的内部资源阈值继续明确标为不可再细分。',f'The authoritative matrix has {failed_request_count} conversions without target body text, including {error_outputs} browser error-page output. Each now links from its detail page to direct evidence and a verified cause. The anomaly review covers the 36 deterministically triggered results; it is not a manual editorial-quality review of every remaining normal result. Undisclosed service-internal resource thresholds remain explicitly indivisible.')+'

'+link('../analysis/index.html#inspect?request=failed',h('查看没有正文的结果','Inspect results without body text'))+'

') if fixture_correction: correction_rows=[] @@ -3599,21 +3715,20 @@ def evidence_links(urls): for name in names: ident=identities[name] provider_rows.append(('',cell(labels[name])+cell(esc(ident.get('reported_version') or h('托管后端未公开 / nightly 无版本命令','Hosted backend undisclosed / nightly has no version command')))+cell(''+esc(ident.get('binary_sha256') or '—')+'')+cell(esc(ident.get('deployment'))))) - adoption=hero(h('方法介绍','Methods'),h('比较六种执行配置,其中 Moli 1.1.5 与 1.1.7 分别保留结果。','The comparison covers six configurations, including separate results for Moli 1.1.5 and 1.1.7.')) + adoption=hero(h('方法介绍','Methods'),h('比较本轮登记的 {method_count} 种执行配置;版本与接口见下表。','Compare the {method_count} registered configurations; versions and interfaces are listed below.')) adoption+=section(h('本轮实现','Measured implementations'),table([h('方法','Method'),h('版本','Version'),h('二进制 SHA-256','Binary SHA-256'),h('部署','Deployment')],provider_rows,metrics=[])) interface_rows=[] - routes={ - 'kitesurf':(h('Cloudflare Browser Run /markdown?browser=kitesurf','Cloudflare Browser Run /markdown?browser=kitesurf'),h('POST html=官方冻结 HTML;拒绝外部资源类型','POST html=official frozen HTML; reject external resource types'),h('服务端原生 Markdown','Server-native Markdown')), - 'lightpanda':('Lightpanda CLI',h('fetch --dump markdown;loopback HTTPS 冻结页面','fetch --dump markdown; frozen loopback HTTPS page'),h('CLI 原生 Markdown','CLI-native Markdown')), - 'obscura':('Obscura CLI',h('fetch --dump markdown;loopback HTTPS 冻结页面','fetch --dump markdown; frozen loopback HTTPS page'),h('CLI 原生 Markdown','CLI-native Markdown')), - 'moli':('Moli CLI',h('fetch --dump markdown;loopback HTTPS 冻结页面','fetch --dump markdown; frozen loopback HTTPS page'),h('CLI 原生 Markdown','CLI-native Markdown')), - 'moli-1.1.7':('Moli CLI',h('fetch --dump markdown;loopback HTTPS 冻结页面','fetch --dump markdown; frozen loopback HTTPS page'),h('CLI 原生 Markdown','CLI-native Markdown')), - 'chromium':('Playwright Chromium',h('渲染冻结页面 DOM → node-html-markdown 2.0.0','Render frozen page DOM → node-html-markdown 2.0.0'),h('转换后的 Markdown','Converted Markdown')), - } + from .report_interfaces import method_spec, _observed_route + from .methods import report_method_catalog + language='en' if english else 'zh' for name in names: example=next((r for r in records if r['provider']==name and r['ok']),next(r for r in records if r['provider']==name)) - entry,input_route,output_route=routes[name] - official=report_method(name).get('source_url','') + entry=method_spec(name,example,language=language)['entry'] + metadata=example.get('provider_metadata',{}) + input_route=_observed_route(native_browser_engine(name,example),metadata,language=language) + output_route=report_method_catalog(language)['output_contract_labels'].get(metadata.get('output_contract'),h('未记录','Not recorded')) + official=browser_method_registry().get(name,{}).get('source_url') or report_method(name).get('source_url','') + interface_rows.append(('',cell(link('../providers/'+name+'.html',labels[name]))+cell(esc(entry))+cell(esc(input_route))+cell(esc(output_route))+cell(link('../attempts/'+example['attempt_id']+'.html',h('查看实际输入、返回和逐项评分','Actual input, output and component scores')))+cell(link(official,h('官方获取入口','Official source'))))) adoption+=section(h('本轮输入、输出与实例','Run inputs, outputs and examples'),'

'+h('这里列的是公开集冻结 HTML 的实际路线,不是原 URL 的实时抓取接口。实例链接对应存档调用;账号字段只展示变量名和占位符。','These are the frozen-HTML routes used for this public benchmark, not live fetching of original URLs. Example links open archived calls; account fields show variable names or placeholders only.')+'

'+table([h('方法','Method'),h('接口 / 程序','Interface / program'),h('本轮输入','Run input'),h('本轮输出','Run output'),h('真实调用实例','Archived call example'),h('官方来源','Official source')],interface_rows,cls='wide-prose-table',metrics=[])) if research: @@ -3635,12 +3750,12 @@ def evidence_links(urls): evidence_rows=[('',cell(link(repo['url'],repo['repo']))+cell('
'.join(link(match['url'],match['path']) for match in repo['matches']))) for repo in item['repositories']] details.append('
'+esc(label)+f' · {n} '+h('个候选仓库','candidate repositories')+''+table([h('仓库','Repository'),h('命中文件','Matched files')],evidence_rows,metrics=[] )+'
') data_prefix='../../adoption/' if english else '' - adoption+=section(h('开源采用线索','Open-source adoption evidence'),'

'+h('这份独立源码快照包括已登记方法,不只包括本轮六种方法。搜索候选有排序、活跃度和星数门槛;数值是已核验候选集中的签名命中,不是全网份额或生产用户数。点击方法可核对仓库和文件。','This independent source snapshot covers registered methods, not only the six measured configurations. Search candidates have ranking, activity and star thresholds; the counts are verified signature hits in the bounded candidate set, not global share or production users. Open a method to inspect repositories and files.')+'

'+esc(adoption_data['generated_at'])+' · '+link(data_prefix+'evidence.json',h('下载完整来源证据','Full source evidence'))+' · '+link(data_prefix+'counts.json',h('下载逐方法统计','Per-method counts'))+'

'+table([h('方法','Method'),h('命中仓库','Matched repositories'),h('命中文件','Matched files')],count_rows,'wmb-adoption-counts',True,metrics=[])+''.join(details)) + adoption+=section(h('开源采用线索','Open-source adoption evidence'),'

'+h('这份独立源码快照包括已登记方法,不只包括本轮{method_count} 种方法。搜索候选有排序、活跃度和星数门槛;数值是已核验候选集中的签名命中,不是全网份额或生产用户数。点击方法可核对仓库和文件。','This independent source snapshot covers registered methods, not only the {method_count} measured configurations. Search candidates have ranking, activity and star thresholds; the counts are verified signature hits in the bounded candidate set, not global share or production users. Open a method to inspect repositories and files.')+'

'+esc(adoption_data['generated_at'])+' · '+link(data_prefix+'evidence.json',h('下载完整来源证据','Full source evidence'))+' · '+link(data_prefix+'counts.json',h('下载逐方法统计','Per-method counts'))+'

'+table([h('方法','Method'),h('命中仓库','Matched repositories'),h('命中文件','Matched files')],count_rows,'wmb-adoption-counts',True,metrics=[])+''.join(details)) _wmb_write(out,'adoption/index.html',h('方法介绍','Methods'),adoption,english) reproduce=hero(h('环境与复现','Reproduction'),h('数据、评分器、浏览器与结果都由哈希或提交固定。','Dataset, scorer, browsers and outputs are fixed by hashes or commits.')) - reproduce+=section(h('核心复现指令','Core reproduction commands'),'

'+h('以下命令使用本仓库锁定依赖;运行六种方法和官方评分需要先取得公开母集、固定提交的评分器和本轮方法二进制。评测凭据只从仓库本地 .env 读取,不进入报告。命令中的尖括号为本机路径占位符。','The commands use this repository’s locked dependencies. Running all six methods and official scoring first requires the public parent set, the pinned scorer checkout and the measured binaries. Evaluation credentials come only from the local repository .env and are not included in this report. Angle brackets denote local paths.')+'

uv run --frozen --extra headless-eval python scripts/run_webmainbench_benchmark.py --provider <name> --case-ids-file evaluation/public-webmainbench-200.json --output <method-evidence>\nuv run --frozen --extra webmainbench-eval python scripts/score_webmainbench_results.py --evidence <method-evidence> --dataset <WebMainBench_545.jsonl> --scorer-repo <pinned-scorer-checkout>\nuv run --frozen python scripts/assemble_webmainbench_evidence.py --source-root <six-method-batches> --output <assembled-evidence> --dataset <WebMainBench_545.jsonl> --sample-manifest evaluation/public-webmainbench-200.json --review <independent-review.json>\nuv run --frozen --extra webmainbench-eval python scripts/correct_webmainbench_teds.py --source <assembled-evidence> --output <corrected-evidence> --scorer-repo <pinned-scorer-checkout>\nuv run --frozen --extra webmainbench-eval python scripts/review_webmainbench_teds_correction.py --source <assembled-evidence> --corrected <corrected-evidence>\nmake public-benchmark-report EVIDENCE=<corrected-evidence>
') - reproduce+=section(h('固定标识','Pinned identities'),f'

Parent dataset SHA-256: {esc(dataset["source_dataset_sha256"])}

Sample manifest SHA-256: {esc(dataset["sample_manifest_sha256"])} · {link("../sample-manifest.json" if not english else "../../sample-manifest.json",h("下载 200 页清单","Download 200-page manifest"))}

Official scorer commit: 9d991bdc00c57b57521499494d96be85c31317ba

') + reproduce+=section(h('核心复现指令','Core reproduction commands'),'

'+h('以下命令使用本仓库锁定依赖;运行{method_count} 种方法和官方评分需要先取得公开母集、固定提交的评分器和本轮方法二进制。评测凭据只从仓库本地 .env 读取,不进入报告。命令中的尖括号为本机路径占位符。','The commands use this repository’s locked dependencies. Running all {method_count} methods and official scoring first requires the public parent set, the pinned scorer checkout and the measured binaries. Evaluation credentials come only from the local repository .env and are not included in this report. Angle brackets denote local paths.')+'

uv run --frozen --extra headless-eval python scripts/run_webmainbench_benchmark.py --provider <name> --case-ids-file <sample-manifest> --output <method-evidence>\nuv run --frozen --extra webmainbench-eval python scripts/score_webmainbench_results.py --evidence <method-evidence> --dataset <WebMainBench_545.jsonl> --scorer-repo <pinned-scorer-checkout>\nuv run --frozen python scripts/assemble_webmainbench_evidence.py --source-root <six-method-batches> --output <assembled-evidence> --dataset <WebMainBench_545.jsonl> --sample-manifest <sample-manifest> --review <independent-review.json>\nuv run --frozen --extra webmainbench-eval python scripts/correct_webmainbench_teds.py --source <assembled-evidence> --output <corrected-evidence> --scorer-repo <pinned-scorer-checkout>\nuv run --frozen --extra webmainbench-eval python scripts/review_webmainbench_teds_correction.py --source <assembled-evidence> --corrected <corrected-evidence>\nmake public-benchmark-report EVIDENCE=<corrected-evidence>
') + reproduce+=section(h('固定标识','Pinned identities'),f'

Parent dataset SHA-256: {esc(dataset["source_dataset_sha256"])}

Sample manifest SHA-256: {esc(dataset["sample_manifest_sha256"])} · {link("../sample-manifest.json" if not english else "../../sample-manifest.json",h("下载 {case_count} 页清单","Download {case_count}-page manifest"))}

Official scorer commit: 9d991bdc00c57b57521499494d96be85c31317ba

') batch_rows=[] for manifest in manifests: batch_rows.append(('',cell(labels[manifest['provider']])+cell(esc(manifest['started_at']))+cell(esc(manifest['completed_at']))+cell(str(manifest['case_count']))+cell(str(manifest['timeout_seconds']))+cell(''+esc(manifest['runner_sha256'])+''))) @@ -3649,7 +3764,7 @@ def evidence_links(urls): release_path=out/'report-changes.json' if not release_path.is_file(): - release_data={'schema_version':2,'releases':webmainbench_history(json.loads((ROOT/'research/report-changes.json').read_text(encoding='utf-8')))} + release_data={'schema_version':2,'releases':[]} release_path.write_text(json.dumps(release_data,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') else: release_data=json.loads(release_path.read_text(encoding='utf-8')) @@ -3657,12 +3772,13 @@ def evidence_links(urls): ('latency-corrections.json','查看逐页纠正收据','Inspect the per-page correction receipt'), ('prior-latency-results.jsonl','下载原始逐页结果','Download prior per-page results'), ) if latency_corrected else () - changes=render_webmainbench_history(release_data,english,release_links) + changes=(render_webmainbench_history(release_data,english,release_links) if release_data.get('releases') else '

'+h('尚无发布记录。','No releases recorded.')+'

') _wmb_write(out,'changes/index.html',h('变更记录','Changes'),changes,english) account_rows=[] for name in names: - access=method_access(name,[]) + access=method_access(name,records) + account_rows.append(('',cell(labels[name])+cell(esc(access['account']))+cell('
'.join(''+esc(v)+'' for v in access['env_vars']))+cell('
'.join(link(url,label) for label,url in access['links'])))) accounts=hero(h('账号与安装','Accounts and setup'),h('只有 Kitesurf 的 Cloudflare 路线需要账号凭据;报告不公开值。','Only the Kitesurf Cloudflare route needs account credentials; values are never published.')) accounts+=section(h('配置','Configuration'),table([h('方法','Method'),h('账号','Account'),h('变量','Variables'),h('官方入口','Official source')],account_rows,cls='accounts-table',metrics=[])) @@ -4352,3 +4468,38 @@ def bound_artifact(path, expected, shard_path): "schema_version": 1, "report": "webmainbench-545-full", "languages": ["zh"], "case_count": 545, "provider_count": len(providers), "result_count": len(records), }, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + + +def render_score_audit(out, audit, binding): + """Explain changed decisions from one bound review, beside native results.""" + def verdict(value):return '通过' if value else '失败' + rows=[] + for item in audit['providers']: + rows.append((' data-audit-summary-provider="'+esc(item['provider'])+'"',cell(esc(provider_label(item['provider'])))+cell(str(item['published_passes']))+cell(str(item['historical_passes']))+cell(str(item['initial_rescore_passes']))+(cell(str(item['original_definition_passes'])) if 'original_definition_passes' in item else '')+cell(str(item['corrected_passes']))+cell(str(item['total'])))) + body=hero(audit['title'],audit['summary']) + body+=section('判定变化来自哪里','
对同一份保存输出核对正文术语、文字和链接格式、卡片字段归属,以及原始与公开副本哈希
'+esc(audit['figure_caption'])+'
') + body+=section('版本结果对照','

'+esc(audit['metric_note'])+'

'+table(['方法','原页面汇总','按旧规则核对旧记录','修复前重算']+(['原定义+当前评分器'] if all('original_definition_passes' in item for item in audit['providers']) else [])+['修订定义+当前评分器','题数'],rows,id='score-audit-comparison',metrics=[])) + body+=section(esc(audit['focus_heading']), '

'+esc(audit['focus_summary'])+'

') + inventory=[] + for item in audit['changes']: + identifier=item['attempt_id'] + inventory.append(('',cell(esc(item['provider']))+cell(esc(item['case_id']))+cell(verdict(item['historical_pass']))+cell(verdict(item['initial_rescore_pass']))+cell(verdict(item['after_fix_pass']))+cell(esc(item['classification']))+cell(link('#'+identifier,'原因与证据')))) + controls='' + markup=table(['方法','题目','旧判定','修复前','修复后','原因分类','证据'],inventory,metrics=[]) + # Keep paragraphs in the evidence sections, outside the fixed-height inventory. + from bs4 import BeautifulSoup + document=BeautifulSoup(markup,'html.parser') + for element,item in zip(document.select('tbody tr'),audit['changes']):element['data-audit-provider']=item['provider'] + body+=section('逐项判定',controls+'
'+str(document)+'
') + body+=section('脱敏副本的影响','

'+esc(audit['privacy_summary'])+'

'+''.join('

'+esc(text)+'

' for text in audit['privacy_findings'])) + details=[] + for item in audit['changes']: + identifier=item['attempt_id'] + facts=''.join('
'+label+'
'+esc(item[key])+'
' for key,label in [('requirement','验收要求'),('actual','实际返回'),('reason','变化原因'),('treatment','处理结果'),('source_scope','来源范围')]) + evidence=' · '.join(link('../'+('audit/history/' if evidence.get('historical') else '')+evidence['path'],evidence.get('label') or evidence['path']) for evidence in item.get('evidence',[])) + quotations=''.join('

'+esc(quote['label'])+'

'+esc(quote['text'])+'
' for quote in item.get('quotes',[])) + details.append('

'+esc(item['provider']+' · '+item['case_id'])+'

'+esc(item['classification'])+' · '+verdict(item['historical_pass'])+' → '+verdict(item['initial_rescore_pass'])+' → '+verdict(item['after_fix_pass'])+'

'+facts+'
原文、规则与实际输出片段'+quotations+'

'+evidence+'

'+link('../attempts/'+identifier+'.html','完整结果、实际输出和调用日志')+'

') + body+=section('每条变化的依据',''.join(details)) + body+=section('审查范围','

'+esc(audit['review_scope'])+'

'+link('score-changes.json','下载逐条复核记录')+' · '+link('scoring-inputs.json','下载原始与公开评分输入哈希')+'

') + write(out,'audit/index.html',audit['title'],body) + write(out,'review/index.html','核验记录',hero('核验记录','逐题验收依据与数据来源。')+section('核验范围','

'+esc(audit['review_scope'])+'

'+esc(audit['summary'])+'

'+link('../audit/index.html','逐条原因、原文与验收规则')+'

')) diff --git a/harness/web_fetch/report_build.py b/harness/web_fetch/report_build.py index 04319e446..c0765fbcb 100644 --- a/harness/web_fetch/report_build.py +++ b/harness/web_fetch/report_build.py @@ -365,7 +365,7 @@ def build_report(source: Path, output: Path, *, include_english: bool = False, local_audit: bool = False, authored_interpretation: bool | None = None, diagnostic_preview: bool = False, language: str | None = None, show_local_status_banners: bool = True, - presentation_binding: Path | None = None) -> Path: + presentation_binding: Path | None = None, profile_path: Path | None = None) -> Path: from harness.report_languages import includes_english, select_report_language include_english = includes_english(language, include_english) source, output = source.resolve(), output.resolve() @@ -389,7 +389,7 @@ def build_report(source: Path, output: Path, *, include_english: bool = False, require_certifiable_evidence(dataset_probe) if dataset_probe.get('comparison', {}).get('kind') == 'webmainbench_frozen_html_to_markdown': return build_webmainbench_report(source, output, include_english=include_english, - language=language) + language=language, profile_path=profile_path) if not local_audit: require_selection_history(source) dataset = json.loads(contained_path(source,'dataset.json').read_text()) @@ -868,14 +868,17 @@ def validate_webmainbench_anomaly_review(receipt: dict, source: Path, def build_webmainbench_report(source: Path, output: Path, *, include_english: bool = False, - language: str | None = None) -> Path: + language: str | None = None, profile_path: Path | None = None) -> Path: """Build the current public-benchmark report without legacy task predicates.""" from harness.report_languages import includes_english, select_report_language include_english = includes_english(language, include_english) from harness.reporting.assets import copy_bound_assets, load_bound_content authority = Path(__file__).resolve().parents[2] - content_binding_path = authority / 'evaluation/webmainbench-content-binding.json' + if profile_path is None: + raise ValueError('WebMainBench report builds require an explicit profile_path') + profile = json.loads(profile_path.read_text()) + content_binding_path = profile_path.parent / 'webmainbench-content-binding.json' content_binding = json.loads(content_binding_path.read_text(encoding='utf-8')) content_source = data_home() / 'report-assets/webmainbench-sample' report_content = load_bound_content( @@ -885,7 +888,8 @@ def build_webmainbench_report(source: Path, output: Path, *, include_english: bo teds_revision = validate_teds_revision_if_present(source, records) summaries = json.loads(contained_path(source, 'score-summaries.json').read_text()) manifests = json.loads(contained_path(source, 'execution.json').read_text())['batches'] - latency_diagnostic = load_latency_diagnostic(source, dataset, records, manifests) + latency_diagnostic = load_latency_diagnostic(source, dataset, records, manifests, + (profile.get('latency_diagnostic') or {}).get('manifest_sha256')) review_path = source / 'result-review.json' review = json.loads(review_path.read_text()) if review_path.is_file() else None if review is not None and review.get('bindings', {}).get('results_jsonl_sha256') != file_sha256(prior_results_path(source)): @@ -898,7 +902,7 @@ def build_webmainbench_report(source: Path, output: Path, *, include_english: bo corrections = json.loads(corrections_path.read_text(encoding='utf-8')) anomaly_review_path = Path(__file__).resolve().parents[2] / 'research/webmainbench-anomaly-review.json' anomaly_review = json.loads(anomaly_review_path.read_text(encoding='utf-8')) - release_path = Path(__file__).resolve().parents[2] / 'research/report-changes.json' + release_path = profile_path.parent.parent / 'research/report-changes.json' release_history = webmainbench_history(json.loads(release_path.read_text(encoding='utf-8'))) fixture_correction_path = source / 'fixture-correction.json' fixture_correction = json.loads(fixture_correction_path.read_text(encoding='utf-8')) if fixture_correction_path.is_file() else None @@ -1000,7 +1004,7 @@ def public_webmainbench_scoring_projection(records: list[dict]) -> list[dict]: return projected -def build_webmainbench_full_report(source: Path, output: Path) -> Path: +def build_webmainbench_full_report(source: Path, output: Path, *, authority_root: Path | None = None) -> Path: """Build the separately bound complete 545-case report from certified evidence.""" from harness.web_fetch.webmainbench_evidence import digest, read_jsonl @@ -1011,7 +1015,10 @@ def build_webmainbench_full_report(source: Path, output: Path) -> Path: ) if source.is_symlink() or any(path.is_symlink() for path in source.rglob('*')): raise ValueError('Symlinked report evidence is forbidden') - profile_path = Path(__file__).resolve().parents[2] / 'evaluation/webmainbench-full/current-report.json' + if authority_root is None: + raise ValueError('Full-cohort builds require an external --authority-root bundle') + authority_root = authority_root.resolve() + profile_path = authority_root / 'evaluation/webmainbench-full/current-report.json' profile = json.loads(profile_path.read_text(encoding='utf-8')) certificate = json.loads((source / 'cohort-certificate.json').read_text(encoding='utf-8')) scoring_certificate = json.loads((source / 'scoring-certificate.json').read_text(encoding='utf-8')) @@ -1026,7 +1033,7 @@ def build_webmainbench_full_report(source: Path, output: Path) -> Path: raise ValueError('Complete scoring certificate mismatch') dataset = json.loads((source / 'dataset.json').read_text(encoding='utf-8')) records = read_jsonl(source / 'results.jsonl') - repo = Path(__file__).resolve().parents[2] + repo = authority_root registry_path = repo / 'evaluation/webmainbench-full/moli-variants.json' registry = json.loads(registry_path.read_text(encoding='utf-8')) from .webmainbench_evidence import read_method_binding @@ -1087,12 +1094,12 @@ def build_webmainbench_full_report(source: Path, output: Path) -> Path: validate_reference_revision(source) if (source / 'scoring-correction.json').is_file(): validate_webmainbench_scoring_revision(source) - content_binding_path = Path(__file__).resolve().parents[2] / 'evaluation/webmainbench-full/content-binding.json' + content_binding_path = authority_root / 'evaluation/webmainbench-full/content-binding.json' content_binding = json.loads(content_binding_path.read_text(encoding='utf-8')) content_path = data_home() / 'report-assets/webmainbench-full' / content_binding['relative_path'] from harness.reporting.assets import load_bound_content load_bound_content(data_home() / 'report-assets/webmainbench-full', content_binding) - failure_binding_path = Path(__file__).resolve().parents[2] / 'evaluation/webmainbench-full/failure-analysis-binding.json' + failure_binding_path = authority_root / 'evaluation/webmainbench-full/failure-analysis-binding.json' failure_binding = json.loads(failure_binding_path.read_text(encoding='utf-8')) failure_path = data_home() / 'report-assets/webmainbench-full' / failure_binding['relative_path'] if digest(failure_path) != failure_binding['sha256']: @@ -1201,7 +1208,7 @@ def build_webmainbench_full_report(source: Path, output: Path) -> Path: from harness.web_fetch.webmainbench_report_check import ( check as check_webmainbench_full, ) - check_webmainbench_full(staging) + check_webmainbench_full(staging, authority_root=authority_root) if output.exists(): backup = Path(tempfile.mkdtemp(prefix='.report-swap-', dir=output.parent)) / 'report' output.rename(backup) @@ -1230,7 +1237,14 @@ def build_wcxb_report(source: Path, corpus: Path, sample_path: Path, profile: di source, output = source.resolve(), output.resolve() providers = profile['providers'] if not providers or len(set(providers)) != len(providers): - raise ValueError('WCXB profile must declare unique participants') + raise ValueError('WCXB participant registry must be nonempty and unique') + for provider in providers: + safe_identifier(provider) + comparison = profile.get('paired_comparison') or {} + baseline, candidate_id = comparison.get('baseline'), comparison.get('candidate') + if comparison and (baseline == candidate_id or {baseline,candidate_id} - set(providers)): + raise ValueError('WCXB paired comparison must name two registered participants') + from harness.reporting.assets import load_bound_content report_content = load_bound_content( source / "report-assets", content_binding, @@ -1261,52 +1275,55 @@ def build_wcxb_report(source: Path, corpus: Path, sample_path: Path, profile: di if [x['provider'] for x in certificate['providers']] != providers: raise ValueError('WCXB participant registry differs from the certificate') from harness.web_fetch.wcxb_scoring import paired_latency_methods - paired_methods = paired_latency_methods(profile, certificate) + paired_methods = paired_latency_methods(profile, certificate) if profile.get('latency_correction_receipt_sha256') else {} rows = load_jsonl(source / 'wcxb-scores.jsonl', skip_blank=True) case_ids = {r['track_id'] for r in records} - correction_root = source / 'latency-correction' - correction_receipt = correction_root / 'receipt.json' - if (not correction_receipt.is_file() - or file_sha256(correction_receipt) - != profile.get('latency_correction_receipt_sha256')): - raise ValueError('WCXB full-scope latency correction is missing or differs from profile') - _, corrected_calls = load_full_paired_latency(correction_root, profile, case_ids) - correction_certificate = json.loads((correction_root / 'certification.json').read_text()) - if (correction_certificate.get('kind') != 'certified_wcxb_paired_latency' - or correction_certificate.get('complete') is not True - or correction_certificate.get('receipt_sha256') - != file_sha256(correction_receipt)): - raise ValueError('WCXB corrected timing is not certified') - if any( - abs(row['total_elapsed_ms'] - corrected_calls[row['case_id'], - paired_methods[row['provider']]]['elapsed_ms']) > .01 - for row in rows if row['provider'] in paired_methods - ): - raise ValueError('WCXB reported paired latency differs from the selected paired round') - optional_paths = [name for name, sha in content_binding['evidence'].items() - if sha == profile.get('optional_ui_receipt_sha256')] - if len(optional_paths) != 1: - raise ValueError('Optional feature receipt must have one evidence binding') - optional_receipt = contained_path(source, optional_paths[0], reject_symlinks=True) - optional_root = optional_receipt.parent - optional_public = Path('research') / ('wcxb-' + optional_root.name) - if (not optional_receipt.is_file() or optional_receipt.is_symlink() - or file_sha256(optional_receipt) - != profile.get('optional_ui_receipt_sha256')): - raise ValueError('WCXB optional UI-stripping evidence is missing or differs from profile') - optional = json.loads(optional_receipt.read_text()) - if (optional.get('case_id') not in case_ids or optional.get('source_sha256') != corpus_hash - or optional.get('builds', {}).get('release', {}).get('binary_sha256') != profile['release_binary_sha256'] - or optional.get('builds', {}).get('candidate', {}).get('binary_sha256') != profile['candidate_binary_sha256'] - or any(not isinstance(build.get('description_present'), bool) - for build in optional['builds'].values())): - raise ValueError('WCXB optional UI-stripping comparison does not bind both builds') - for name in ('release', 'candidate'): - for ext, key in (('md', 'output_sha256'), ('stderr', 'stderr_sha256')): - artifact = optional_root / f'{name}.{ext}' - if (not artifact.is_file() or artifact.is_symlink() - or file_sha256(artifact) != optional['builds'][name][key]): - raise ValueError('WCXB optional UI-stripping artifact changed: ' + artifact.name) + if profile.get('latency_correction_receipt_sha256'): + correction_root = source / 'latency-correction' + correction_receipt = correction_root / 'receipt.json' + if (not correction_receipt.is_file() + or file_sha256(correction_receipt) + != profile.get('latency_correction_receipt_sha256')): + raise ValueError('WCXB full-scope latency correction is missing or differs from profile') + _, corrected_calls = load_full_paired_latency(correction_root, profile, case_ids) + correction_certificate = json.loads((correction_root / 'certification.json').read_text()) + if (correction_certificate.get('kind') != 'certified_wcxb_paired_latency' + or correction_certificate.get('complete') is not True + or correction_certificate.get('receipt_sha256') + != file_sha256(correction_receipt)): + raise ValueError('WCXB corrected timing is not certified') + if any( + abs(row['total_elapsed_ms'] - corrected_calls[row['case_id'], + paired_methods[row['provider']]]['elapsed_ms']) > .01 + for row in rows if row['provider'] in paired_methods + ): + raise ValueError('WCXB reported paired latency differs from the selected paired round') + if profile.get('optional_ui_receipt_sha256'): + optional_paths = [name for name, sha in content_binding['evidence'].items() + if sha == profile.get('optional_ui_receipt_sha256')] + if len(optional_paths) != 1: + raise ValueError('Optional feature receipt must have one evidence binding') + optional_receipt = contained_path(source, optional_paths[0], reject_symlinks=True) + optional_root = optional_receipt.parent + optional_public = Path('research') / ('wcxb-' + optional_root.name) + if (not optional_receipt.is_file() or optional_receipt.is_symlink() + or file_sha256(optional_receipt) + != profile.get('optional_ui_receipt_sha256')): + raise ValueError('WCXB optional UI-stripping evidence is missing or differs from profile') + optional = json.loads(optional_receipt.read_text()) + if (optional.get('case_id') not in case_ids or optional.get('source_sha256') != corpus_hash + or optional.get('builds', {}).get('release', {}).get('binary_sha256') != profile['release_binary_sha256'] + or optional.get('builds', {}).get('candidate', {}).get('binary_sha256') != profile['candidate_binary_sha256'] + or any(not isinstance(build.get('description_present'), bool) + for build in optional['builds'].values())): + raise ValueError('WCXB optional UI-stripping comparison does not bind both builds') + for name in ('release', 'candidate'): + for ext, key in (('md', 'output_sha256'), ('stderr', 'stderr_sha256')): + artifact = optional_root / f'{name}.{ext}' + if (not artifact.is_file() or artifact.is_symlink() + or file_sha256(artifact) != optional['builds'][name][key]): + raise ValueError('WCXB optional UI-stripping artifact changed: ' + artifact.name) + if len(rows) != len(records) * len(providers) or {(r['provider'], r['case_id']) for r in rows} != {(p,c) for p in providers for c in case_ids}: raise ValueError('WCXB score matrix is incomplete') if output.exists() and not (output / '.generated-report').is_file(): @@ -1318,12 +1335,15 @@ def build_wcxb_report(source: Path, corpus: Path, sample_path: Path, profile: di shutil.copy2(source / name, staging / name) shutil.copy2(sample_path, staging / 'sample-manifest.json') (staging / 'research').mkdir() - (staging / 'research/wcxb-full-paired-latency').mkdir() - for name in ('receipt.json', 'certification.json', 'calls.jsonl'): - shutil.copy2(correction_root / name, staging / 'research/wcxb-full-paired-latency' / name) - (staging / optional_public).mkdir() - for name in ('receipt.json', 'release.md', 'candidate.md', 'release.stderr', 'candidate.stderr'): - shutil.copy2(optional_root / name, staging / optional_public / name) + if profile.get('latency_correction_receipt_sha256'): + (staging / 'research/wcxb-full-paired-latency').mkdir() + for name in ('receipt.json', 'certification.json', 'calls.jsonl'): + shutil.copy2(correction_root / name, staging / 'research/wcxb-full-paired-latency' / name) + if profile.get('optional_ui_receipt_sha256'): + (staging / optional_public).mkdir() + for name in ('receipt.json', 'release.md', 'candidate.md', 'release.stderr', 'candidate.stderr'): + shutil.copy2(optional_root / name, staging / optional_public / name) + shutil.copy2(Path(__file__).resolve().parents[2] / 'research/report-changes.json', staging / 'research/report-changes.json') shutil.copy2(Path(__file__).resolve().parents[2] / 'research/provider-adoption.json', diff --git a/harness/web_fetch/report_check.py b/harness/web_fetch/report_check.py index 92a7b36d1..c895e0914 100644 --- a/harness/web_fetch/report_check.py +++ b/harness/web_fetch/report_check.py @@ -9,7 +9,7 @@ from pathlib import Path from urllib.parse import parse_qsl, urlencode, urlsplit -from harness.record_io import load_jsonl +from harness.record_io import file_sha256, load_jsonl from harness.report_languages import check_language_contract, generated_languages, visible_numbers from harness.reporting.presentation import check_presentation from harness.web_fetch.reference_lifecycle import validate_resource_source_evidence @@ -287,7 +287,7 @@ def check_local_diagnostic_presentation(root: Path, *, ranked=False): if ranked and 'ranking' not in disclosure.visible: raise ValueError('Sample ranking scope missing: ' + str(path.relative_to(root))) for table in re.findall(r']*\bid="ranking-[^"]+".*?', page, re.S): - if not ranked and ('data-dir="desc"' in table or re.search(r'\s*(?:序号|Rank|No\.)\s*', table)): + if not ranked and re.search(r'\s*(?:序号|Rank|No\.)\s*', table): raise ValueError('Uncertified diagnostic comparison assigns ranks: ' + str(path.relative_to(root))) @@ -300,14 +300,15 @@ def check(root: Path, *, authority_root: Path, profile: Path | None = None, pres profile_path = ( (repository / profile).resolve() if not profile.is_absolute() else profile.resolve() ) - if not profile_path.is_relative_to((repository / "evaluation").resolve()): - raise ValueError("--profile must stay under evaluation/") + if (root / 'report-manifest.json').is_file() and json.loads((root / 'report-manifest.json').read_text()).get('evidence_state') == 'published_snapshot_score_audit': + from .published_snapshot import check_snapshot_audit + if profile_path and file_sha256(profile_path) != json.loads((root / 'report-manifest.json').read_text()).get('profile_sha256'): + raise ValueError('Snapshot audit profile changed') + return check_snapshot_audit(root) if (root / "wcxb-score-summary.json").is_file(): - if profile_path and profile_path != repository / "evaluation/wcxb/current-report.json": - raise ValueError("WCXB report requires its own current profile") from harness.web_fetch.wcxb_report_check import check as check_wcxb - check_wcxb(root, authority_root=repository) + check_wcxb(root, authority_root=repository, profile_path=profile_path) return None if profile_path: require_current_report_profile(root, profile_path) @@ -349,7 +350,7 @@ def check(root: Path, *, authority_root: Path, profile: Path | None = None, pres if d.get("comparison", {}).get("kind") == "webmainbench_frozen_html_to_markdown": from harness.web_fetch.webmainbench_report_check import check_sample_report - check_sample_report(root, d, rs, authority_root=repository) + check_sample_report(root, d, rs, authority_root=repository, profile_path=profile_path) return ( "PASS: WebMainBench preselected-200 matrix, sample, scores, retries, " "declared-language routes and reference assets verified" @@ -413,9 +414,9 @@ def check(root: Path, *, authority_root: Path, profile: Path | None = None, pres canonical_dataset = repository / "evaluation/dataset.json" if d.get("comparison", {}).get("kind") in {"headless_html", "headless_markdown"}: headless = json.loads((repository / "configs/providers.headless.json").read_text()) - profile = json.loads( - (profile_path or repository / "evaluation/current-report.json").read_text() - ) + if profile_path is None: + raise ValueError("Headless report checks require --profile") + profile = json.loads(profile_path.read_text()) if profile.get("comparison_kind") not in {"headless_html", "headless_markdown"}: raise ValueError( "Legacy headless report cannot be checked against a different " @@ -770,20 +771,24 @@ def main(argv=None, *, authority_root: Path | None = None): parser.add_argument('--source-dataset', type=Path) parser.add_argument("--authority-root", type=Path, default=authority_root or Path.cwd()) parser.add_argument("--presentation-binding", type=Path) + parser.add_argument("--visual-receipt",type=Path,help="Full snapshot-audit browser checks; save receipt outside the report") args = parser.parse_args(argv) + if args.visual_receipt: + from .published_snapshot import check_snapshot_visual + print(check_snapshot_visual(args.root,args.visual_receipt)) result = check(args.root, authority_root=args.authority_root, profile=args.profile, presentation_binding=args.presentation_binding, source_dataset=args.source_dataset) if result is not None: print(result) -def check_revision(out: Path, *, authority_root: Path): +def check_revision(out: Path, *, authority_root: Path, profile_path: Path | None = None): reject_evidence_symlinks(out) out = out.resolve() repository = authority_root.resolve() if (out / "wcxb-score-summary.json").is_file(): from harness.web_fetch.wcxb_report_check import check as check_wcxb - check_wcxb(out, authority_root=repository) + check_wcxb(out, authority_root=repository, profile_path=profile_path) return "PASS: WCXB release scope and report acceptance checks verified" dataset = json.loads((out / "dataset.json").read_text()) from bs4 import BeautifulSoup @@ -791,7 +796,7 @@ def check_revision(out: Path, *, authority_root: Path): if dataset.get("comparison", {}).get("kind") == "webmainbench_frozen_html_to_markdown": from harness.web_fetch.webmainbench_report_check import check_sample_report - check_sample_report(out, authority_root=repository) + check_sample_report(out, authority_root=repository, profile_path=profile_path) return ( "PASS: WebMainBench preselected-200 release scope and report acceptance checks verified" ) @@ -971,9 +976,10 @@ def check_revision(out: Path, *, authority_root: Path): def revision_main(argv=None, *, authority_root: Path | None = None): parser = argparse.ArgumentParser(description="Report presentation and adoption checks") parser.add_argument("root", type=Path, nargs="?") + parser.add_argument("--profile", type=Path) parser.add_argument("--authority-root", type=Path, default=authority_root or Path.cwd()) args = parser.parse_args(argv) result = check_revision( - args.root or args.authority_root / "reports/webfetch", authority_root=args.authority_root + args.root or args.authority_root / "reports/webfetch", authority_root=args.authority_root, profile_path=args.profile ) print(result) diff --git a/harness/web_fetch/report_components.py b/harness/web_fetch/report_components.py index 54c8e3c2a..068c0e989 100644 --- a/harness/web_fetch/report_components.py +++ b/harness/web_fetch/report_components.py @@ -1,5 +1,6 @@ """Small, shared presentation components; never change measured results.""" from html import escape +from pathlib import Path import json from dataclasses import dataclass @@ -112,78 +113,7 @@ def metric_legend(headers): return '
'+''.join('
'+escape(k)+'
'+escape(v)+'
' for k,v in entries)+'

'+missing+'

' -TRACK_CONTEXT = { - 'web': { - 'flow': ['请求网页', '定位正文区域', '按题目范围保留文字', '输出正文'], - 'flow_note': '目标是读出页面内容,而不是保存整份网页源码。正文若已在响应中,普通 HTTP 加解析器就可能完成。', - 'meaning': '从文章、文档站、百科或项目主页中取回题目要求的内容。导航和页脚是否需要保留,由每道题登记的范围决定;它们不能替代要求的正文、列表或表格。', - 'example': '项目仓库页以 README 为正文:只返回仓库名称和简介不够;把文件目录、导航一起带回来,也可能降低纯净度。', - 'boundary': '这组按正文提取任务选样,没有逐页按渲染方式分组,不能等同于“纯静态网页”。请求成功、任务通过、全文质量分别衡量;不测站内搜索、点击或多页浏览。', - }, - 'dynamic': { - 'flow': ['取得页面外壳', '浏览器运行页面', '加载数据或嵌入子页', '目标内容就绪', '提取正文与字段'], - 'flow_note': '这是常见的浏览器渲染路径,可由 Web Fetch 内部完成,无需先手动打开网页。只取初始 HTML 可能缺正文;但若能直接解析内嵌数据、请求公开接口或读取子页,也不必走完浏览器渲染流程。', - 'meaning': '这里的“动态”指内容的加载方式,不是文字经常更新。目标正文不直接以完整可读文本出现在初始页面中,还需异步取数、客户端组装、等待呈现或加载嵌入子页。难点是从页面壳找到真正的数据。', - 'example': 'Quotes 异步引文页:只拿到页面标题不够,要取得首批引文及每条对应的作者、标签;不能把不同条目的字段拼在一起。', - 'case_id': 'dynamic-quotes-api', - 'boundary': '只测各案例声明的初次自动加载范围,不要求滚动、翻页或点击。执行 JavaScript 不是唯一合格路线,解析内嵌数据或调用公开接口也可。固定演示文字仍核对内容,易变字段不固定旧值。', - }, - 'challenge': { - 'flow': ['请求目标网站', '识别内容页或受限页', '提取稳定说明与栏目', '提取列表与详情链接'], - 'flow_note': '接口正常返回也可能只是登录页或验证页。只有到达目标内容,并满足本例条目要求,才算任务通过。', - 'meaning': '从有风控、登录限制或复杂内容组织的热门网站提取任务正文:既要取得列表,也要保留登记的栏目、说明或固定字段标签。只返回页面壳、登录提示或几个孤立链接,不足以满足有正文核对项的任务。', - 'example': 'IMDb:取得至少三部不同影片的名称与详情链接,还须保留榜单标题和“由常规用户投票”的说明;影片名称、名次与评分不固定为旧值。', - 'case_id': 'challenge-09', - 'boundary': '不向方法提供站点登录凭据,不操作验证码;某一次访问受限不等于网站失效。标题完整度、纯净度和 F1 只衡量返回条目与独立记录名称的文字重合,不测整页正文、榜单召回、实时排名或价格。标题本身也可能被编辑,文字差异不直接改判任务结果。', - }, - 'risk_control': { - 'flow': ['请求指定网址', '识别目标页或拦截页', '提取首次加载内容', '核对稳定文字与完整正文', '核对列表结构与数量'], - 'flow_note': '测试不点击、不输入、不滚动、不翻页,也不登录。返回验证页、空白页或只有页面外壳都不能代替目标内容。', - 'meaning': '从曾在保存的观察中出现登录、验证、反自动化或访问限制的指定网址取得目标内容。资格只解释样本来源;评分按独立保存的目标页面参考核对,防止只取得标题、开头、导航或少量列表条目也被判为通过。', - 'example': '文章页须覆盖直接加载出的完整正文;列表页须保留稳定栏目,并取得满足域名、路径、字段、去重和数量要求的名称与真实链接。', - 'boundary': '规则只覆盖指定网址首次自动加载出的可见目标内容,不要求绕过验证或执行交互。通过表示本次返回满足冻结规则,不证明网站以后不会改变,也不证明所有访问环境都能取得相同页面。', - }, - 'document': { - 'flow': ['打开文件或查看页', '定位实际文档', '解析页面、段落与表格', '输出文档文字'], - 'flow_note': '下载链接只是入口;正文提取还需读取文件内容。内嵌查看器的菜单、按钮不是文档正文。', - 'meaning': '读取 PDF、DOCX、TXT 以及网页内嵌文档中的文字。与普通网页不同,正文可能在文件或查看器后面;难点是识别文档并保留跨页、段落和表格中的内容。', - 'example': 'Attention Is All You Need PDF:需要论文正文并命中标题、Transformer 等核对点;只返回下载地址不通过,只提取首页会丢失后续全文。', - 'case_id': 'document-01', - 'boundary': '衡量文字提取,不衡量原版排版还原、图表理解,也不能据此推断扫描件 OCR 能力。拿到文件与读出文件内容是两件事,全文质量仍单独计算。', - }, - 'image': { - 'flow': ['读取含图页面', '定位指定正文图片', '解析实际资源地址', '返回图片链接'], - 'flow_note': '这是含图网页的任务路径。直接图片 URL 属于另一类文件任务:请求图片 → 返回原始字节 → 校验文件,不能用链接代替文件。', - 'meaning': '定位网页中的目标图片,或取得直接图片文件。难点是区分正文图片、缩略图和装饰图,并保留可用的资源地址;这不是让方法描述图片里画了什么。', - 'example': 'MDN 响应式图片页:返回参考清单中的两张图片地址。文字写着“这里有图片”不算命中;直接 PNG 任务则不能仅复述输入网址。', - 'case_id': 'image-01', - 'boundary': '资源命中只证明返回了指定地址,不证明图片字节已下载或内容已理解。历史原文件记录缺少字节验收证据,不能用于断言某产品不支持下载。', - }, - 'video': { - 'flow': ['读取视频页面', '定位播放器或信息区', '解析媒体入口或文字', '交付本例指定结果'], - 'flow_note': '按题目返回播放地址,或标题与发布者,不要求每题同时完成所有步骤。直接视频 URL 另走文件下载与字节校验路径。', - 'meaning': '分别测试网页播放入口、直接视频文件,以及视频标题和发布者。难点是辨别播放器外壳、实际媒体地址与文字信息;这三类输出不能互相替代。', - 'example': 'YouTube 的 Me at the zoo 信息任务核对标题与发布者 jawed;MDN 视频页另测播放地址。前者通过,不代表已经下载或看懂了视频。', - 'case_id': 'video-09', - 'boundary': '不测画面理解、语音转写或字幕质量。找到播放入口不等于取得视频字节;历史调用缺少原文件字节验收,相关记录显示未测,不计为产品失败。', - }, - 'web_media': { - 'flow': ['请求媒体网页', '定位指定资源或信息', '提取地址或文字', '交付题目要求的结果'], - 'flow_note': '按各题登记的要求返回资源地址、文字或结构。', - 'meaning': '从网页中找到指定图片、视频资源或视频入口地址,或读取视频页登记的文字信息。这里测的是网页取数,不是图片或视频理解。', - 'example': '含图页面须返回登记的图片地址;视频信息页须返回标题与发布者。拿到地址不代表已经取得对应文件。', - 'case_id': 'image-01', - 'boundary': '地址题只核对目标地址,文字题只核对登记信息;不检查画面、字幕、声音,也不要求下载媒体字节。', - }, - 'media_file': { - 'flow': ['请求直接文件地址', '接收响应字节', '识别文件格式', '核对长度与 SHA-256'], - 'flow_note': '输入直接指向图片或视频文件,评测对象是返回的原始字节,不进行 Markdown 转换。', - 'meaning': '取得直接媒体文件。必须返回可验证的图片或视频字节;复述网址、返回文件名或网页文字都不能代替文件。', - 'example': '直接 PNG 地址须交付与独立保存样本一致的文件格式、长度和 SHA-256;只返回该 PNG 的网址不通过。', - 'case_id': 'image-10', - 'boundary': '这组只验证文件交付,不测试图片描述、视频理解、语音转写或字幕质量。', - }, -} +TRACK_CONTEXT = json.loads((Path(__file__).resolve().parent / 'data/report-track-context.json').read_text(encoding='utf-8')) def media_population_text(track, cases): @@ -215,6 +145,7 @@ def task_pass_guide(track, base='', cases=None): 'challenge': '请求成功,取得至少 3 条具名详情链接,字段完整、类型正确、条目不重复,链接符合目标站点规则;有独立内容证据的稳定标题、栏目、说明还须命中。频繁变化的名单和价格不固定旧值。通过表示满足上述取数要求,不证明变化值真实或最新。', 'risk_control': '请求成功并取得指定目标页面;正文分区、组合规则和登记为必需的稳定文字均须满足。登记为诊断的稳定文字只帮助核对抓取范围,不影响是否通过。文章不能只返回标题或开头,列表须达到登记的字段、真实链接、路径、去重和数量要求;验证页、空返回和页面外壳均不通过。', } + rules.update({key: value['pass_rule'] for key, value in TRACK_CONTEXT.items() if 'pass_rule' in value}) text = rules.get(track, '按案例中登记的验收规则逐项检查。') from .report_i18n import TEXT missing_translations = { @@ -521,3 +452,90 @@ def sources_panel(): ('PDF.js','直接 PDF 与嵌入文档阅读器案例。'), ('MDN 媒体示例','图片和视频入口案例。'), ]) + + +def acceptance_scope(cases, records): + """Describe frozen requirements and saved outcomes without changing either.""" + from collections import Counter, defaultdict + from urllib.parse import urlsplit + from lexbench_webfetch.metrics import task_passed + + families = defaultdict(Counter) + affiliated = [] + for case in cases.values(): + counts = families[case.get('track', 'web')] + contract = case.get('acceptance_contract') or {} + counts['tasks'] += 1 + for key, present in { + 'identity': case.get('page_identity_required', False), + 'literal': bool(case.get('expected_any') or contract.get('stable_text_anchors')), + 'facts': bool(case.get('required_facts')), + 'body_regions': bool((case.get('body_requirements') or {}).get('regions')), + 'records': bool(contract.get('minimum_records', 0)), + 'resources': bool(case.get('expected_resources')), + 'full_reference': bool(case.get('ground_truth_main_content') and case.get('content_score_applicable', True)), + 'short_length_floor': case.get('min_content_chars', 1) <= 1, + }.items(): + counts[key] += bool(present) + url = urlsplit(case.get('url', '')) + if url.hostname == 'github.com' and (url.path.rstrip('/') == '/lexmount/moli' or url.path.startswith('/lexmount/moli/')): + # The benchmark maintainer and this evaluated repository share an + # organization. The frozen task remains in the original population. + affiliated.append(case['id']) + methods = defaultdict(Counter) + for row in records: + case = cases[row['case_id']] + metrics = row.get('metrics') or {} + counts = methods[row['provider']] + counts['results'] += 1 + passed = task_passed(row['ok'], metrics, case.get('task_kind', 'body')) + counts['passes'] += passed + counts['identity_verified'] += metrics.get('page_identity_status') == 'verified' + counts['unverified_identity_passes'] += bool(passed and metrics.get('page_identity_status') != 'verified') + counts['blocked_diagnostics'] += bool(metrics.get('blocked_page') or metrics.get('stable_blocked_page')) + if case.get('expected_resources'): + counts['resource_results'] += 1 + counts['unexpected_resources'] += len(metrics.get('unexpected_resource_urls') or []) + precision = metrics.get('resource_precision') + if isinstance(precision, (int, float)) and not isinstance(precision, bool): + counts['resource_precision_sum'] += precision + counts['resource_precision_count'] += 1 + return {'families': dict(families), 'methods': dict(methods), 'affiliated_cases': affiliated} + + +def render_acceptance_scope(cases, records): + from .report import model_text as h, section, table, cell, esc, pct, provider_label, link + scope = acceptance_scope(cases, records) + fields = ('tasks', 'identity', 'literal', 'facts', 'body_regions', 'records', 'resources', 'full_reference', 'short_length_floor') + rows = [('', cell(esc(track)) + ''.join(cell(str(counts[key])) for key in fields)) + for track, counts in sorted(scope['families'].items())] + body = '

' + h( + '下表统计每类冻结任务声明了哪些要求。一题可以使用多项要求。固定文字、事实组和列表结构分别列出;没有正文范围或全文参考的任务,不据此声称测过完整正文质量。', + 'The table counts requirements declared by frozen tasks. A task may use several checks. Literal text, fact groups and record structure are separate; tasks without body regions or a full reference do not establish full-body quality.') + '

' + body += table([h('任务类别', 'Task family'), h('任务数', 'Tasks'), h('要求页身份', 'Identity required'), + h('固定文字', 'Literal text'), h('事实组', 'Fact groups'), h('正文范围', 'Body regions'), + h('记录结构', 'Records'), h('资源目标', 'Resource targets'), h('全文参考', 'Full reference'), h('长度下限≤1', 'Minimum length ≤1')], rows, metrics=[]) + rows = [] + for name, counts in sorted(scope['methods'].items()): + unverified = counts['unverified_identity_passes'] + passes = counts['passes'] + precision = (counts['resource_precision_sum'] / counts['resource_precision_count'] + if counts['resource_precision_count'] else None) + values = (str(counts['results']), str(passes), str(counts['identity_verified']), + str(unverified) + '/' + str(passes) + ' (' + pct(unverified / passes if passes else None) + ')', + str(counts['blocked_diagnostics']), str(counts['resource_results']), + str(counts['unexpected_resources']), pct(precision)) + rows.append(('', cell(esc(provider_label(name))) + ''.join(cell(value) for value in values))) + body += '

' + h( + '通过但未核验页身份的结果仍按原合同计分,表中以全部通过结果为分母列出比例。拦截提示仅作诊断。资源多余地址数和平均精确率按资源任务统计;召回合同不会因此增加事后门槛。语言默认比较主标签,只有任务声明 exact 时才要求地区标签完全一致。', + 'Passes without verified page identity retain their frozen verdicts; their share uses all passes as its denominator. Blocked-page markers are diagnostic. Unexpected URLs and mean precision describe resource tasks without adding a new recall threshold. Language checks compare the primary tag unless a task declares exact regional matching.') + '

' + body += table([h('方法', 'Method'), h('结果数', 'Results'), h('通过数', 'Passes'), + h('身份已核验', 'Identity verified'), h('通过但身份未核验 / 全部通过', 'Unverified identity passes / all passes'), + h('拦截提示', 'Blocked markers'), h('资源结果数', 'Resource results'), + h('多余资源地址数', 'Unexpected resource URLs'), h('平均资源精确率', 'Mean resource precision')], rows, metrics=[]) + if scope['affiliated_cases']: + body += '

' + h('利益关系:以下任务涉及评测维护方同一组织下的产品仓库;保留原任务和原分母。', + 'Affiliation: these tasks concern a product repository in the benchmark maintainer’s organization. The original tasks and denominator are retained.') + ' ' + ' · '.join( + link('../datasets/' + identifier + '.html', esc(identifier)) + for identifier in scope['affiliated_cases']) + '

' + return section(h('验收覆盖与诊断范围', 'Acceptance coverage and diagnostics'), body, 'acceptance-scope') diff --git a/harness/web_fetch/report_explainer.py b/harness/web_fetch/report_explainer.py index 368e0690f..4598f10e6 100644 --- a/harness/web_fetch/report_explainer.py +++ b/harness/web_fetch/report_explainer.py @@ -342,7 +342,8 @@ def distribution(rows): 'evidence': browser_example_evidence(out,cases[row['case_id']],row,focus=item.get('focus'),reference_focus=item.get('reference_focus'))}) stats = {} from .report_interfaces import method_provenance - from .methods import method_version + from .methods import method_versions + provenance = method_provenance(out) for name in providers: rows = [indexed[name, cid] for cid in cases] @@ -355,7 +356,7 @@ def distribution(rows): explanation = chapter.get('explanation') if not isinstance(explanation, str) or not explanation.strip(): raise ValueError('Browser chapter requires a measured explanation') - versions = sorted({str(method_version(row,provenance) or '') for row in rows} - {''}) + versions = method_versions(rows,provenance) groups = {track: [row for row in rows if report_family(cases[row['case_id']]) == track] for track in tracks} final_times=[] physical_calls=0 @@ -408,7 +409,12 @@ def evidence(path, anchor, title, payload): payload = '

完整实际返回

'+escape(raw)+'
' payload += '

结果与评分

'+escape(json.dumps({key:row[key] for key in ('attempt_id','provider','case_id','run_id','started_at','elapsed_ms','ok','status_code','error_type','metrics') if key in row},ensure_ascii=False,indent=2))+'
' for binding in record_public_call_log_bindings(row): - log = verify_public_call_log(out,binding).read_text() + snapshot = json.loads((out/'dataset.json').read_text()).get('comparison',{}).get('score_audit') if (out/'dataset.json').is_file() else None + if snapshot: + from .published_snapshot import read_snapshot_public_file + log = read_snapshot_public_file(out,binding).read_text() + else: + log = verify_public_call_log(out,binding).read_text() payload += '

调用日志

'+escape(log)+'
' evidence('../attempts/'+row['attempt_id']+'.html', 'offline-result-'+row['attempt_id'], '完整结果与调用证据 · '+name, payload) @@ -809,7 +815,7 @@ def append_html(parent, markup): for figure in figures_by_section['conclusion']:parts['conclusions'].insert(1,figure) if previous: both=sum(new[cid] and old[cid] for cid in cases);neither=sum(not new[cid] and not old[cid] for cid in cases) - changes=[('两版都通过',both),('修复版变为通过',gains),('修复版变为失败',losses),('两版都失败',neither)] + changes=[('两种配置都通过',both),('仅'+label(primary)+'通过',gains),('仅'+label(previous)+'通过',losses),('两种配置都失败',neither)] append_html(parts['conclusions'],'

同一批题的两版结果 · '+str(model['cases'])+' 题

'+''.join('

'+title+'

'+str(count)+'题
' for i,(title,count) in enumerate(changes))+'
同一验收标准,所有任务都保留在对照中;变化是在线调用的实际结果,不单独证明代码因果。
') chapters=[('scope','01 这次测什么',['scope']),('results','02 通过率、耗时与资源',['result-decomposition','all-latency','timing-boundary','successful-latency','resources']),('method','03 测试方法与实例',['scoring','examples']),('configuration','04 浏览器与测试配置',['interfaces']),('failures','05 失败任务与正文缺失',['body-quality','failure-summary']+['browser-'+name for name in order]),('conclusion','06 结论与适用范围',['conclusions'])] toc='' diff --git a/harness/web_fetch/report_i18n.py b/harness/web_fetch/report_i18n.py index cc940fea0..84d310bb7 100644 --- a/harness/web_fetch/report_i18n.py +++ b/harness/web_fetch/report_i18n.py @@ -15,10 +15,6 @@ from pathlib import Path, PurePosixPath from urllib.parse import urlsplit, urlunsplit -from harness.web_fetch.report_i18n_adoption_a import TEXT as ADOPTION_TEXT_A -from harness.web_fetch.report_i18n_adoption_b import TEXT as ADOPTION_TEXT_B -from harness.web_fetch.report_i18n_adoption_c import TEXT as ADOPTION_TEXT_C -from harness.web_fetch.report_i18n_adoption_d import TEXT as ADOPTION_TEXT_D NAV = [ @@ -36,2324 +32,36 @@ # Exact editorial translations take priority. Dynamic evidence (task names, # references and provider output) is deliberately absent from this catalog. -TEXT = { - "先检查请求与返回内容,再逐项检查本例声明的稳定文字、正文覆盖与组合规则;诊断文字只显示命中情况。": "Check the request and returned content, then apply the declared stable text, body coverage and composite requirements. Diagnostic text reports matches without changing the verdict.", - "本轮所有本地方法的每个主机名和跳转都使用固定的 Cloudflare HTTPS DNS,且不回退到系统 DNS。Moli、Lightpanda 和 Obscura 通过进程内解析器取得同一组公网地址;Chromium 使用自身的严格安全 DNS 配置。各浏览器原有的私网访问保护保持不变。": "Every local method uses the fixed Cloudflare HTTPS DNS service for each hostname and redirect, with no fallback to system DNS. Moli, Lightpanda, and Obscura receive the same public addresses through a process-scoped resolver; Chromium uses its own strict secure-DNS profile. Each browser keeps its existing private-network protection.", - "Kitesurf 在 Cloudflare Browser Run 中执行,使用云端网络。它的任务结果可以按同一验收规则比较,但耗时包含不同的部署位置和公网路径,不能据此判断本地浏览器引擎谁更快。": "Kitesurf runs in Cloudflare Browser Run and uses its cloud network. Its task outcomes use the same acceptance rules, but its latency includes a different deployment location and public-network path and therefore cannot rank local browser-engine speed.", - "让同一次原生 Markdown 调用包含已加载子文档;不切换输出格式": "Includes loaded child documents in the same native Markdown call without switching output format", - "CLI fetch --dump markdown → 内置转换 → Markdown;动态正文整类统一启用 --layout 和 --with-frames,不使用逐题 selector": "CLI fetch --dump markdown → built-in conversion → Markdown; every dynamic-body task uses --layout and --with-frames, with no task-specific selector", - "按题目要求预先选定参评浏览器的官方原生接口。网页正文和文档正文都必须以可读 Markdown 交付;除 Chromium 使用固定的 HTML-to-Markdown 包外,不会改取 HTML、JSON DOM 或 PDF/DOCX 字节后替选手提取正文。网页内嵌媒体题只检查原生输出中的指定图片、视频或条目地址,媒体文件不下载。只有直接媒体文件题返回原始字节,并校验类型、文件头、长度和 SHA-256;这是二进制文件交付,不冒充 Markdown。下表按固定任务输入列出处理链;失败后不切换接口或引擎。": "Each task is assigned an official native interface before execution. Webpage and document body tasks must deliver readable Markdown. Chromium is the sole exception: it uses the fixed HTML-to-Markdown package because it has no native Markdown output. The benchmark does not take HTML, a JSON DOM, or PDF or DOCX bytes and extract body text on a participant's behalf. Webpage media-information tasks check only the required image, video, or entry URL in native output and do not download the media. Direct-media-file tasks alone return original bytes, which are checked for type, signature, length, and SHA-256. That binary deliverable is not presented as Markdown. The table lists the route fixed for each task input; a failure never switches interface or engine.", - "这批调用未传入 Moli 内部超时参数。": "These calls did not pass Moli internal-timeout arguments.", - "这批调用使用保存记录中的显式 Moli 超时,未写成产品默认值。": "These calls used the explicit Moli timeouts preserved in their records; the report does not describe them as product defaults.", - "保存证据未提供可核对的 Moli 内部超时策略。": "The saved evidence does not expose a verifiable Moli internal-timeout policy.", - "CLI · 按任务预选原生 Markdown、HTML 或原始响应路线": "CLI · preselects native Markdown, HTML, or raw-response routes by task", - "网页内嵌媒体": "Embedded webpage media", - "直接媒体文件": "Direct media files", - "直接取得 Moli 原生 HTML;共同评分器只识别其中的资源 URL": "Returns Moli's native HTML directly; the shared scorer only identifies resource URLs in it", - "所有正文与结构任务使用 Moli 原生 Markdown。旧版 selector 合同只映射到官方等待参数,不参与正文抽取。图片、视频资源页使用原生 HTML,共同评分器只识别其中的资源 URL。直接 PDF/DOCX 仍调用同一原生接口;接口没有交付可读正文时按失败记录,评测端不下载后解析。文件使用原生 CDP 响应或同一二进制的原始附件接口。最终网址仅作诊断,不决定正文是否通过;失败后不切换格式或借用其他 provider。": "All body and structured tasks use Moli's native Markdown. Legacy selector contracts map only to official wait parameters and do not extract body text. Image and video resource pages use native HTML, from which the shared scorer only identifies resource URLs. Direct PDF and DOCX inputs still use the same native interface; if it does not deliver readable text, the task fails and the benchmark does not download and parse the file. File tasks use native CDP responses or the same binary's raw attachment interface. The final URL is diagnostic only and does not determine body-task acceptance. A failure never triggers a format switch or another provider.", - "moli fetch;正文使用原生 Markdown,资源页使用原生 HTML,预声明完成条件只调用官方等待参数,直接文件使用原始响应": "moli fetch; body tasks use native Markdown, resource pages use native HTML, preregistered completion conditions only call official wait parameters, and direct files use raw responses", - "资源页原生 HTML 中的图片地址": "Image URLs in native HTML from the resource page", - "资源页原生 HTML 中的媒体地址": "Media URLs in native HTML from the resource page", - "直接取得 Moli 原生 HTML;共同评分器只识别其中的图片 URL": "Returns Moli's native HTML directly; the shared scorer only identifies image URLs in it", - "直接取得 Moli 原生页面与网络记录;共同评分器只识别其中的视频 URL": "Returns Moli's native page and network record directly; the shared scorer only identifies video URLs in it", - "所有正文与结构任务使用 Moli 原生 Markdown。旧版 selector 合同只映射到官方等待参数,不参与正文抽取。网页图片地址题使用原生 HTML;网页视频地址题使用原生 JSON 页面与网络记录,并在 done 后统一留出 5 秒加载窗口。共同评分器只识别原生输出中的资源 URL。直接 PDF/DOCX 仍调用同一原生接口;接口没有交付可读正文时按失败记录,评测端不下载后解析。文件使用原生 CDP 响应或同一二进制的原始附件接口。最终网址仅作诊断,不决定正文是否通过;失败后不切换格式或借用其他 provider。": "All body and structured tasks use Moli's native Markdown. Legacy selector contracts map only to official wait parameters and do not extract body text. Webpage image-URL tasks use native HTML. Webpage video-URL tasks use the native JSON page and network record, with a uniform five-second loading window after done. The shared scorer only identifies resource URLs in the native output. Direct PDF and DOCX inputs still use the same native interface; if it does not deliver readable text, the task fails and the benchmark does not download and parse it. File tasks use native CDP responses or the same binary's raw attachment interface. The final URL is diagnostic only and does not determine body-task acceptance. A failure never triggers a format switch or another provider.", - "查看本报告的任务范围、结果数量与验收规则。": "See the tasks, result counts and acceptance rules used in this report.", - "内容选择": "Content selection", - "典型处理流程": "Typical processing flow", - "失败环节": "Failure stage", - "失败类别": "Failure category", - "搜索": "Search", - "搜索案例、方法或原因": "Search tasks, methods or causes", - "类别": "Category", - "示例说明 · Web Fetch": "Example notes · Web Fetch", - "未开启 debug": "Debug logging disabled", - "未设置": "Not configured", - "云端 API 后端未公开版本": "Cloud API backend version not disclosed", - "使用本轮任务配置与方法配置运行,或从保存的结果重新构建报告:": "Run with this task and method configuration, or rebuild the report from saved results:", - "重新运行会调用配置中的方法;重建报告只读取已有结果。本轮参数、版本与可执行文件哈希见上表。": "Rerunning calls the configured methods. Rebuilding reads saved results only. Parameters, versions and executable hashes are listed above.", - "至少需要两种方法才能比较任务交集。": "At least two methods are needed to compare shared tasks.", - "· 未保存源页快照": "· No source-page snapshot saved", - "本轮方法": "Methods in this run", - "运行前准备配置对应的程序与账户。报告保留方法名称、版本和执行证据,不包含凭据值。实际参数与版本见": "Install the configured programs and set up any required accounts before running. The report saves method names, versions and execution evidence, without credential values. See parameters and versions in", - "方法 SHA-256": "Method SHA-256", - "这次物理调用没有保存可展示的 stderr / debug 日志;不会补造不存在的日志。": "No displayable stderr or debug log was saved for this physical call.", - "下载最终物理调用记录": "Download the final physical-call record", - "。运行记录保留请求状态、耗时和证据路径;原始输出保持调用后保存的字节。": ". The record retains request status, duration and evidence paths. Raw output preserves the saved response bytes.", - "请求成功,并同时满足本例最低字符数、全部必需核对点、显著标记及结构要求;不得命中非参考正文自身包含的错误页标记。完整度、纯净度和 F1 另算,不作为通过门槛。": "A successful request must satisfy the task's minimum length, all required text checks, salient markers and structure requirements. Error-page markers absent from the reference must not appear. Recall, precision and F1 are calculated separately and do not determine acceptance.", - "云端服务未公开后端版本": "The cloud service does not disclose its backend version", - "未取得可确认的版本信息": "No verifiable version information is available", - "本轮使用版本/源码提交": "Version / source commit used in this run", - "显示本轮使用的软件版本或源码提交。云端服务未公开可确认的后端版本号时,直接说明;这不代表服务没有版本。": "Software versions or source commits used for this run. Undisclosed cloud backend versions are stated explicitly; this does not mean the service has no version.", - "本轮启用自身 DEBUG 并捕获网络库日志。": "This run enabled native DEBUG output and captured network-library logs.", - "列出本轮核验后的修正;修正读取已保存的返回,不代表重新调用产品。": "Corrections after verification of this run use saved responses; they do not represent new product calls.", - "本轮结果修正": "Corrections to this run", - "详细依据": "Supporting evidence", - "核验范围与修正说明": "Review scope and corrections", - "产品原生返回;保留实际输出与日志": "Native product output; actual responses and logs are retained", - "缓存与等待选项见本轮固定配置": "See the frozen configuration for cache and wait settings", - "Firecrawl、Tavily、Exa、Cloudflare;通过各自 API 调用。": "Firecrawl, Tavily, Exa and Cloudflare, called through their respective APIs.", - "本轮实际固定配置": "Frozen settings used in this run", - "本轮保存了哪些日志": "Logs retained in this run", - "产品与依赖库日志范围": "Product and dependency log coverage", - - "比例只对应本报告列出的案例与配置。先看总榜和分榜,再打开具体案例,对照验收要求与实际返回。请求成功只表示方法正常返回;任务通过表示返回内容满足该案例的全部要求。": "Rates apply only to the cases and configurations in this report. Start with the overall and category rankings, then open individual cases to compare requirements with actual output. Request success means the method returned normally; task success means the returned content met every requirement for that case.", - "评分规则与计时范围见“评测方法”,检查和修正记录见“核验记录”,方法版本、固定配置与日志范围见“环境与复现”。": "See Methodology for scoring and timing, Review history for checks and corrections, and Reproduce for method versions, fixed options and log coverage.", - "已保存的诊断日志": "Saved diagnostic logs", - "已保存的诊断日志 · Web Fetch": "Saved diagnostic logs · Web Fetch", - "完整原始响应": "Complete original response", - "完整原始响应 · Web Fetch": "Complete original response · Web Fetch", - "查看完整 API 响应(已隐藏凭据与本机路径)": "View complete API response (credentials and local paths hidden)", - "查看已保存的诊断日志(已隐藏凭据与本机路径)": "View saved diagnostic logs (credentials and local paths hidden)", - "已隐藏凭据与本机路径": "Credentials and local paths hidden", - "真实保存记录;仅为浏览显示,未增加产品调用。": "Actual saved records, formatted for browsing without additional product calls.", - "记录来源": "Record source", - "下载原始公开文件": "Download original public file", - "仅包含实际观测的日志。": "Contains only observed logs.", - "产品实际返回;公开副本已隐藏凭据与本机路径。": "Actual product response; credentials and local paths are hidden in this public copy.", - "或": "or", - "稳定内容与结构检查通过。": "Stable content and structure checks passed.", - "条目结构检查通过;本例未设置稳定文字核对点。": "Record structure passed; this task has no stable-text checks.", - "稳定文字或记录结构没有满足任务要求。": "Stable text or record structure did not meet the task requirements.", - "全部文字组都须命中。": "Every text group must match.", - "完整参考正文": "Complete reference body", - "查看原始验收规则": "View raw acceptance rules", - "正文长度要求": "Body length requirement", - "· 公开 SHA-256:": "· Public SHA-256:", - "已保存文件截断;原生日志范围见说明": "Saved file truncated; see native log coverage notes", - "已保存文件未截断;原生日志范围见说明": "Saved file not truncated; see native log coverage notes", - "本次没有取得产品或依赖库的原生日志。已保存的调用方记录见下方。": "No native product or dependency logs were obtained for this call. Saved caller records appear below.", - "实际诊断日志 · 产品与依赖库": "Observed diagnostic logs \u00b7 products and dependencies", - "仅展示真实观测记录;未开放的内部过程不作推断。": "Only observed records are shown; unavailable internal steps are not inferred.", - "返回 JSON 在“原始返回”中单独展示;接入程序记录在下方“调用方事件”中。": "Returned JSON is shown separately under raw responses. Adapter records appear below under caller events.", - "调用方事件(测试程序记录)": "Caller events (test program records)", - "下载运行时日志(已分离返回内容)": "Download runtime logs (returned content separated)", - "已保存真实诊断(原生日志范围见说明)": "Observed diagnostics saved (see native log coverage notes)", - "本轮未重新核验来源。": "Sources were not reverified for this run.", - "本地实测版": "Local measured comparison", - "本地未认证诊断快照 · 不可用于排名": "Local uncertified diagnostic snapshot · not for ranking", - "来源未在调用前后完成全量核验。保存的评分、质量指标和复核记录仅供诊断,不代表正式发布或认证结果。": "Sources were not fully verified before and after calls. Saved scores, quality metrics and review records are diagnostic evidence, not a certified release.", - "本地诊断结果,不提供名次。": "Local diagnostic results; no ranks are assigned.", - "任务判定汇总(非排名)": "Task judgments (not a ranking)", - "按任务类别查看诊断结果": "Diagnostic results by task family", - "方法按名称排列,不提供名次。任务判定、内容质量和耗时分别展示。": "Methods are listed by name without ranks. Task judgments, content quality and latency are shown separately.", - "本地未认证诊断结果,仅展示保存的任务判定与耗时,不用于产品排名。通过不代表返回内容干净;请同时查看全文指标和挑战提示。": "Local uncertified diagnostic results show saved task judgments and latency, not product rankings. Passing does not establish clean content; also inspect full-text metrics and challenge indicators.", - "诊断指标说明": "Diagnostic metric definitions", - "无命中": "No matches", - "评分修正账目": "Scoring revision ledger", - "每轮增加、减少与最终通过数均从逐条状态记录计算;排除结果保留原始调用,不计入比较分母。": "Additions, removals and final passes are derived from individual result states. Excluded results retain their original calls and stay outside comparison denominators.", - "阶段": "Stage", - "修正前通过": "Passes before", - "新增通过": "Passes added", - "减少通过": "Passes removed", - "修正后通过": "Passes after", - "下载逐条增减账目": "Download per-result revision ledger", - "通过结果的内容诊断": "Content diagnostics for passing results", - "目标覆盖与附加内容": "Target coverage and additional content", - "目标要求:": "Target requirements:", - "附加内容诊断:": "Additional-content diagnostics:", - "参考词精确率:": "Reference word precision:", - "包含验证码组件文字": "Challenge-widget text is present", - "未检测到该组件文字特征": "No matching widget-text signature detected", - "排除比较": "Excluded from comparison", - "通过只表示满足登记的目标要求,不保证干净正文。提示词命中不证明整页被拦截;词精确率也受参考范围和时间影响。低于 30% 仅用于定位检查对象,不是统一失败线。": "Passing means the registered target requirements were met, not that the body is clean. Hint words do not establish that the whole page was blocked; word precision also depends on reference scope and time. Below 30% identifies inspection candidates, not a universal failure threshold.", - "评测环境异常:统一排除比较": "Evaluation environment defect: excluded across all methods", - "已有复核记录;覆盖和裁定依据见核验页面。": "Review records are available; see the review page for coverage and adjudication evidence.", - "结果为机器判定,未进行独立人工审核。": "Results are machine judgments without independent human review.", - "调用链与日志检查": "Call-chain and log audit", - "题目通过": "Task passes", - "诊断日志": "Diagnostic logs", - "原生日志范围": "Native log coverage", - "检查结论": "Audit findings", - "如何读取本轮结果": "How to read these results", - "本轮按保存的实际返回进行机器判分,比例只对应本报告列出的案例与配置。日志可用于复查调用和判分;失败原因须结合原始响应、调用记录及核验记录判断,不能一概归因于产品内部。": "Saved outputs are machine-scored. Rates apply only to the cases and configurations listed here. Assess failure causes using the native response, call log and review record; failures cannot all be attributed to product internals.", - "本轮按保存的实际返回进行机器判分,比例只对应本报告列出的案例与配置。日志可用于复查调用和判分;未经过独立人工审核,不能将失败一概归因于产品内部。": "Saved outputs are machine-scored. Rates apply only to the cases and configurations listed here. Logs support review of calls and scoring; without independent human review, failures cannot all be attributed to product internals.", - "逐次结果与筛选": "Per-call results and filters", - "题目与计时规则": "Tasks and timing rules", - "配置、版本和复现说明": "Configuration, versions, and reproduction", - "本轮固定原生配置": "Fixed native configuration", - "每种方法内部串行执行;方法之间的调度以执行批次和保存的调用时间为准。分段执行保留每个原始分段,完整调用时间见执行批次。": "Each method runs serially internally. Execution batches and saved timestamps document scheduling between methods. Segmented executions retain every original segment; see the execution batches for full call times.", - "每种方法内部串行;方法之间的调度见执行批次。": "Each method runs serially internally; see execution batches for scheduling between methods.", - "本轮保存的调用字段": "Saved request fields from this run", - "此处展示该次公开调用记录保存的请求字段;完整配置和日志见环境与调用详情。产品内部超时以冻结配置和实际请求为准。": "These request fields come from this public call record. See environment and call details for full configurations and logs. Frozen configurations and actual requests determine product timer settings.", - "已测方法的输入展示保存的公开调用字段,输出展示同一次调用的实际节选。完整配置、原始响应和日志见环境与调用详情。未参评方法保留仓库登记示例。": "For measured methods, inputs show saved public request fields and outputs show excerpts from the same call. See environment and call details for full configurations, raw responses, and logs. Unmeasured methods retain registered repository examples.", - "同一任务集合的全部调用耗时": "All-call latency on the same task set", - '验收规则(无全文参考)': 'Acceptance rules (no full-text reference)', - '下载验收规则': 'Download acceptance rules', - '全文完整度、纯净度和 F1 不适用。': 'Full-text completeness, purity and F1 do not apply.', - '本例按左侧文字验收规则判定。': 'This case is judged against the text acceptance rules on the left.', - '本轮方法与接口': 'Methods and interfaces in this run', - '以下内容来自本轮结果记录与适配器契约。': 'These details come from this run’s records and adapter contracts.', - '查看该方法的结果': 'View this method’s results', - '适配器类型': 'Adapter type', - '记录的引擎': 'Recorded engine', - '记录的版本': 'Recorded version', - '记录的提取方式': 'Recorded extractor', - '记录的输出契约': 'Recorded output contract', - '本轮结果未绑定价格资料;费用取决于调用方配置与服务账单。': 'This run has no bound pricing information; costs depend on the caller’s configuration and service billing.', - '本报告的方法由调用方的显式配置提供。': 'The caller’s explicit configuration supplies the methods in this report.', - '方法名称': 'Method name', - '工具版本': 'Tool version', - '原始输出': 'Raw output', - '程序错误': 'Program error', - '分类:': 'Classification: ', - 'Web Fetch · 评测报告': 'Web Fetch · Evaluation report', - 'Web Fetch · 合成示例': 'Web Fetch · Synthetic example', - 'Web Fetch · 诊断运行': 'Web Fetch · Diagnostic run', - '合成示例 · 非正式评测': 'Synthetic example · not a formal benchmark', - '诊断运行 · 非正式评测': 'Diagnostic run · not a formal benchmark', - '文件交付': 'File delivery', - '资源发现': 'Resource discovery', - '结构化内容': 'Structured content', - '网页任务': 'Web tasks', - '文件校验未通过': 'File validation failed', - '已返回文件字节,但格式、长度或目标 SHA-256 校验未通过;逐项证据见文件交付详情': 'File bytes were returned, but format, length or target SHA-256 validation failed; see the file-delivery evidence for individual checks', - '结构化内容按任务登记的字段、类型、数量、唯一性和链接规则验收。': 'Structured content is checked against the task-defined fields, types, counts, uniqueness and link rules.', - '资源发现按任务登记的目标地址及答案字段范围验收。': 'Resource discovery is checked against the task-defined target addresses and answer fields.', - '文件交付须返回原始字节,并通过格式、长度及已登记目标 SHA-256 校验;文字或链接不能替代文件。': 'File delivery requires original bytes that pass format, length and any declared target SHA-256 checks; text or links cannot replace a file.', - '本次调用用于检查所选任务和方法,不具备正式评测所需的批次认证。': 'These calls check the selected tasks and methods without the cohort certification required for a formal benchmark.', - '内容和失败均为预设场景,用于检查安装与报告功能,不代表浏览器测量结果。': 'Content and failures are predefined scenarios for checking installation and report functionality, not browser measurements.', - '合成示例适配器': 'Synthetic example adapter', - '离线检查安装、评分、证据和报告导航。': 'Check installation, scoring, evidence and report navigation offline.', - '示例范围': 'Example scope', - '示例费用': 'Example costs', - '本例只在本机运行测试适配器,不调用收费接口。': 'This example runs a test adapter locally without calling paid services.', - '输入为任务定义与超时预算,输出为预设的内容或错误。完整输出见该方法的结果详情。': 'The input is a task definition and timeout budget; the output is predefined content or an error. Full output is available in the method result details.', - '返回内容由本地测试适配器生成,不访问网页。成功和失败均为预设场景,不能用来比较浏览器能力。': 'A local test adapter generates the content without visiting websites. Both success and failure are predefined scenarios and cannot compare browser capabilities.', - '示例说明': 'About this example', - '合成示例': 'Synthetic example', - '本报告由本地测试适配器生成。没有正式评测发布记录。': 'This report was generated by a local test adapter and has no formal benchmark release history.', - - "Lightpanda、Obscura 与 Moli 的普通网页使用 CLI 正文输出。Moli 的直接文档和媒体 URL 使用裸 fetch:正文任务在预算内解析,文件任务校验字节。": "Lightpanda, Obscura and Moli use CLI body output for ordinary web pages. Moli uses bare fetch for direct document and media URLs: body parsing stays within the time budget and file tasks validate bytes.", - "保留同一次调用的原生 Markdown 和最终页面地址。Lightpanda 解包原生 JSON;Obscura 执行固定哈希的官方 Markdown 脚本,并读取当前地址;页面完成条件不变。": "Retain native Markdown and the final page URL from the same call. Lightpanda unwraps its native JSON; Obscura evaluates the hash-pinned upstream Markdown script and reads the current URL, preserving the page completion conditions.", - "登记配置:": "Registered configuration:", - "只有 Kitesurf 的账户级 Cloudflare Browser Run 路线需要供应商账号与凭据;其余方法是本地二进制,不需要账号,但仍需安装并明确指定可执行文件。": "Only the account-level Kitesurf Cloudflare Browser Run route requires a provider account and credentials; the other methods use local binaries that must be installed and explicitly selected.", - "查看各方法执行结果 →": "View execution results for all methods →", - "浏览器版本与二进制来源": "Browser versions and binary sources", - "风控网页正文": "Risk-control page body text", - "本报告的任务范围与验收规则": "Task scope and acceptance rules", - "查看全部任务及验收规则": "View all tasks and acceptance rules", - "查看结果、实际返回及失败证据": "View results, actual outputs, and failure evidence", - "普通网页使用 fetch --dump markdown;本轮风险网站评测固定 debug 日志并逐次完整留存。": "Ordinary web pages use fetch --dump markdown; this risk-control benchmark pins debug logging and retains the complete log for every call.", - "从官方 v1.1.5 release 获取二进制,配置 MOLI_BINARY;不是 Lexmount 云服务接口。": "Obtain the binary from the official v1.1.5 release and configure MOLI_BINARY; this is not the Lexmount cloud-service interface.", - "Moli 1.1.5 正式发布": "Moli 1.1.5 official release", - "本方法只使用官方 1.1.5 发行二进制,与其他 Moli 版本分开计分。普通网页使用原生 Markdown;直接文档与媒体的裸 fetch 路线在正式二进制上实测确认。正文任务若取得受支持的原始文档,会在同一计时预算内解析文字;文件任务直接验收字节。不在失败后切换引擎或输出模式。": "This method uses only the official 1.1.5 release binary and is scored separately from other Moli versions. Ordinary web pages use native Markdown; bare-fetch routes for direct documents and media were verified on the release binary. If a body-text task retrieves a supported original document, text is parsed within the same timing budget; file tasks validate bytes directly. The method does not switch engines or output modes after a failure.", - "从官方 v1.1.7 release 获取二进制,配置 MOLI_1_1_7_BINARY。": "Obtain the binary from the official v1.1.7 release and configure MOLI_1_1_7_BINARY.", - "Moli 1.1.7 正式发布": "Moli 1.1.7 official release", - "本方法只使用官方 1.1.7 发行二进制,与其他 Moli 版本分开计分。普通网页使用原生 Markdown;直接文档与媒体的裸 fetch 路线在正式二进制上实测确认。正文任务若取得受支持的原始文档,会在同一计时预算内解析文字;文件任务直接验收字节。不在失败后切换引擎或输出模式。": "This method uses only the official 1.1.7 release binary and is scored separately from other Moli versions. Ordinary web pages use native Markdown; bare-fetch routes for direct documents and media were verified on the release binary. If a body-text task retrieves a supported original document, text is parsed within the same timing budget; file tasks validate bytes directly. The method does not switch engines or output modes after a failure.", - "风控网站": "Risk-control websites", - "历史访问受限网址内容": "Historically access-restricted URL content", - "风控网站正文": "Risk-control website body text", - "请求指定网址": "Request the specified URL", - "识别目标页或拦截页": "Identify the target page or a blocked page", - "提取首次加载内容": "Extract the initially loaded content", - "核对稳定文字与完整正文": "Check stable text and the complete body", - "核对列表结构与数量": "Check list structure and quantity", - "测试不点击、不输入、不滚动、不翻页,也不登录。返回验证页、空白页或只有页面外壳都不能代替目标内容。": "The test does not click, type, scroll, paginate, or log in. A verification page, blank response, or page shell cannot substitute for the target content.", - "从曾在保存的观察中出现登录、验证、反自动化或访问限制的指定网址取得目标内容。资格只解释样本来源;评分按独立保存的目标页面参考核对,防止只取得标题、开头、导航或少量列表条目也被判为通过。": "Retrieve target content from specified URLs where saved observations showed login, verification, anti-automation, or access restrictions. Qualification only explains cohort selection; scoring uses independently saved target-page references so a title, opening section, navigation, or a few list entries cannot pass as the full target.", - "文章页须覆盖直接加载出的完整正文;列表页须保留稳定栏目,并取得满足域名、路径、字段、去重和数量要求的名称与真实链接。": "An article page must cover the complete body loaded directly. A list page must retain stable sections and return names with real links that meet the host, path, field, deduplication, and quantity requirements.", - "规则只覆盖指定网址首次自动加载出的可见目标内容,不要求绕过验证或执行交互。通过表示本次返回满足冻结规则,不证明网站以后不会改变,也不证明所有访问环境都能取得相同页面。": "The rules cover only visible target content loaded automatically on the specified URL. They do not require bypassing verification or interacting with the page. A pass means this response satisfies the frozen rules; it does not prove that the site will remain unchanged or that every access environment receives the same page.", - "请求成功并取得指定目标页面;正文分区、组合规则和登记为必需的稳定文字均须满足。登记为诊断的稳定文字只帮助核对抓取范围,不影响是否通过。文章不能只返回标题或开头,列表须达到登记的字段、真实链接、路径、去重和数量要求;验证页、空返回和页面外壳均不通过。": "The request must succeed and return the specified target page. Body-region rules, composite rules, and stable text marked as required must pass. Diagnostic stable text only checks retrieval scope and does not affect acceptance. An article cannot return only its title or opening. A list must meet its declared field, real-link, path, deduplication, and quantity requirements. Verification pages, empty responses, and page shells fail.", - "页面正文按登记的正文规则核验;稳定文字只用于核对抓取范围,不影响是否通过;新闻、日期、轮播卡片、仓库状态等易变内容不固定为答案。历史完整快照只作诊断,不进入全文质量汇总。": "Page text is evaluated against its registered body rules. Stable text only checks retrieval scope and does not affect acceptance. Volatile news, dates, carousel cards, repository status, and similar content are not frozen as answers. Historical full snapshots are diagnostic only and are excluded from full-text quality aggregates.", - "页面正文按登记的正文规则核验;登记的稳定文字必须命中;新闻、日期、轮播卡片、仓库状态等易变内容不固定为答案。历史完整快照只作诊断,不进入全文质量汇总。": "Page text is evaluated against its registered body rules, and registered stable text must match. Volatile news, dates, carousel cards, repository status, and similar content are not frozen as answers. Historical full snapshots are diagnostic only and are excluded from full-text quality aggregates.", - "打开指定网址,取得首次自动加载出的目标正文、列表和真实链接,不进行点击、输入、滚动、翻页或登录。": "Open the specified URL and retrieve the target body, lists, and real links loaded automatically, without clicking, typing, scrolling, pagination, or login.", - "按独立保存的目标页面证据登记稳定文字、完整正文分区,以及名称—链接、路径、字段、去重和数量要求。稳定文字会明确标为必需或诊断。": "Use independently saved target-page evidence to register stable text, complete body regions, and name-link, path, field, deduplication, and quantity requirements. Stable text is explicitly marked as required or diagnostic.", - "先排除请求失败、空返回、验证或拦截页面和页面外壳,再逐项检查本例声明的稳定文字、正文覆盖与组合规则;诊断文字只显示命中情况。": "First exclude request failures, empty responses, verification or blocked pages, and page shells. Then check the case's declared stable text, body coverage, and composite rules; diagnostic text only reports whether it matched.", - "正文与列表规则及标为必需的稳定文字均通过;诊断文字不影响结论。文章只返回标题或开头、列表数量不足、链接不符合真实路径,均不通过。": "Body and list rules and stable text marked as required must pass; diagnostic text does not affect the verdict. An article with only a title or opening, a list below its quantity threshold, or links that do not match real paths fails.", - "动态网站正文整类使用 CLI 原生 domstable": "The complete dynamic-page body family uses the CLI's native domstable mode", - "本轮100例统一使用 Moli 原生 Markdown Fetch 与默认 done 完成方式;除目标 URL 外没有逐例等待元素、额外等待时间、提取脚本或特殊参数。未启用额外的理解模型或付费后备方法。": "All 100 cases use Moli's native Markdown Fetch and default done completion mode. Apart from the target URL, no case-specific wait element, extra delay, extraction script, or special parameter is used. No additional interpretation model or paid fallback is enabled.", - "核验初次加载范围:稳定内容锚点(稳定标题、栏目、说明)与条目的必需字段、数量、唯一性和类型共同决定任务通过。频繁变化的名单、条目标题和价格按声明的结构与类型核验,不固定旧快照取值。HTTP 页面壳不通过;渲染、内嵌数据和公开 API 均可作为实现路径。全文质量仅在有完整、可比的参考时另算。 已登记正文覆盖规则的案例,还须在最多 5 个参考区域中逐区达到 80% 词语计数覆盖,不能只返回标题或开头。 Ajax | Web Scraper Test Sites 的字段覆盖要求:price至少 6 条、description至少 6 条、reviews至少 6 条,各归属于完整且唯一的记录。 React · 商品与分期信息 的字段覆盖要求:分期至少 15 条、配送至少 13 条,各归属于完整且唯一的记录。": "Initial-load acceptance combines stable content anchors (stable headings, sections, and explanations) with each record's required fields, count, uniqueness, and types. Frequently changing lists, item titles, and prices are checked against declared structure and types rather than frozen snapshot values. An HTTP shell alone fails; rendering, embedded data, and public APIs are all valid implementation routes. Full-text quality is calculated separately only when a complete comparable reference exists. Cases with registered body-coverage rules must also reach 80% token-count coverage in each of up to 5 reference regions; returning only a title or opening section is insufficient. For Ajax | Web Scraper Test Sites, field coverage requires at least 6 price, 6 description, and 6 review values, each belonging to a complete unique record. For React · 商品与分期信息, field coverage requires at least 15 installment entries and 13 delivery entries, each belonging to a complete unique record.", - "新增25例结果审核 25/25;未决 0;75例沿用既有审核记录": "Added 25-case result review: 25/25; 0 unresolved. Existing audit records are retained for the prior 75 cases", - "Moli Web Fetch 实测:五款浏览器,150 道任务": "Moli Web Fetch benchmark: five browsers, 150 tasks", - "解读突出耗时减少比例并增加三张信息图": "Interpretation highlights latency reductions and adds three infographics", - "首屏以 Chromium 为基准展示均值、P50、P95 的耗时减少比例;以结果总览和两张流程图替代重复总结与部分流程文字,标题改为直接的内容名称。": "The opening now shows reductions in mean, P50, and P95 latency relative to Chromium. A results overview and two flow diagrams replace repeated summaries and some procedural prose, and the title now names the subject directly.", - "耗时比较": "Latency comparison", - "首屏强调秒数,读者需要自行计算差距。": "The opening emphasized seconds, leaving readers to calculate the differences.", - "用未舍入数据计算相对 Chromium 的耗时减少比例:均值 42.8%、P50 46.7%、P95 44.3%,秒数作为次要依据保留。": "Using unrounded values, the report calculates reductions relative to Chromium: 42.8% for mean latency, 46.7% for P50, and 44.3% for P95; seconds remain as supporting values.", - "仍为跨批次的 98 道共同成功任务;统计样本与测量边界不变。": "The population remains the 98 tasks passed by all methods across batches; the statistical population and measurement boundary are unchanged.", - "信息图": "Infographics", - "概念流程与总结主要依靠文字。": "Conceptual workflows and summaries were mainly presented as prose.", - "增加结果总览、Web Fetch 与 Browser Use 对照、五种交付物流程图;提供完整图片下载,英文版使用对应英文总览。": "Add a results overview, a Web Fetch versus Browser Use comparison, and a diagram of five output workflows; provide full-size downloads and a corresponding English overview.", - "流程图为说明性示意,六组案例仍展示原始证据。图片逐项核对并绑定哈希,不参与评分。": "The diagrams are explanatory. The six case groups still show original evidence; images are checked item by item and hash-bound, but do not affect scoring.", - "解读开篇增加综合通过率与六类结果": "Interpretation opens with overall pass rates and six task groups", - "五浏览器解读先展示综合通过率与六类任务成绩,正文解释差异,结尾归纳结论。浏览器配置改为对照表,章节统一编号。": "The five-browser article opens with overall pass rates and results for six task groups, explains the differences in the body, and closes with findings. Browser configurations use a comparison table, with consistently numbered sections.", - "十方法全矩阵复核与评分根因修订": "Full-matrix review and root-cause scoring revision for ten methods", - "全量审查除既有问题外,又确认了四类共因:正文分段按顺序贪心分配会制造假阴性;Exa 裸链接缺少名称归属,而 richLinks 才提供明确配对;序列化脚本状态会被误当可读正文;DOCX 任务只登记章节标题,无法发现表格、日历和尾注缺失。审查在修正前提出 56 条分歧,其中 2 条直接确认了后两类评分错误。": "Beyond previously identified issues, the full review confirmed four shared causes: greedy region-by-region body matching created false negatives; Exa bare links lacked name ownership while richLinks provided explicit pairs; serialized script state could be mistaken for readable body text; and the DOCX task registered only section headings, so missing tables, calendar content, and endnotes went undetected. Before correction, the review raised 56 disagreements, 2 of which directly confirmed the latter scoring errors.", - "正文覆盖改为顺序无关的全局容量分配;Exa 具名链接题使用官方 richLinks 并只认证重跑受影响的 10 个挑战任务;可读正文拒绝脚本状态壳;DOCX 登记表格、日历和尾注内容地标,并支持等价 Markdown 表格排版。每类根因均有防假阳性与防假阴性的成对可执行回归。": "Body coverage now uses order-independent global capacity allocation. Exa named-link tasks use official richLinks, with certified reruns limited to the 10 affected challenge tasks. Readable-body acceptance rejects script-state shells. The DOCX contract registers table, calendar, and endnote landmarks while accepting equivalent Markdown table layout. Every root cause has paired executable controls against both false positives and false negatives.", - "旧评分实现可能在代码更新后继续被报告读取;分段正文按参考顺序贪心匹配会让较长实际片段先占用错误区域;序列化脚本状态含目标字符串时又可能被当成可读正文。": "A report could keep reading stale scorer output after code changes; greedy body matching in reference order could let longer returned fragments consume the wrong region first; and serialized script state containing target strings could be mistaken for readable body text.", - "报告构建拒绝旧评分实现;正文片段改为全局容量分配且不受参考区域顺序影响;脚本或应用状态壳不能满足正文任务。": "Report builds reject stale scorer output; body fragments now use global capacity allocation independent of reference-region order; and script or application-state shells cannot satisfy body tasks.", - "两类修正均通过公共 score_attempt 与任务判定入口执行正反向回归;保存输出统一离线重算,不按方法或单条结果增加特例。": "Both corrections have positive and negative regressions executed through the shared score_attempt and task-decision paths. Saved output is consistently rescored offline without provider-specific or row-specific exceptions.", - "Exa 具名链接接口": "Exa named-link interface", - "旧适配器只读取裸 links 数组,既无法证明名称与 URL 的归属,又漏用了官方返回明确 anchor—URL 配对的 richLinks。": "The old adapter read only the bare links array, which could not prove name-to-URL ownership and omitted official richLinks with explicit anchor–URL pairs.", - "仅具名链接任务请求 extras.richLinks,并只接纳 anchor 与 URL 均非空的明确配对;正文标题与裸 links 永不按位置猜配。": "Only named-link tasks request extras.richLinks, and only explicit pairs with nonempty anchors and URLs are accepted. Body headings and bare links are never paired by position.", - "接口合同变化只影响 Exa 的 10 个挑战任务,因此通过认证流程重跑该范围;3 条通过、7 条仍因实际产物不满足合同而失败。": "The interface-contract change affects only Exa's 10 challenge tasks, so the certified rerun was limited to that scope. 3 pass; 7 still fail because the actual artifacts do not satisfy the contract.", - "DOCX 完整内容": "Complete DOCX content", - "document-05 只要求标题和章节名,导致返回正文叙述却漏掉表格单元格、日历与最终尾注的结果仍可通过。": "document-05 required only the title and section names, allowing narrative output that omitted table cells, calendar content, and the final notes to pass.", - "任务合同增加来自冻结源文档的表格、日历和尾注内容地标;Markdown 表格分隔符按排版处理,不能把完整表格误判失败。": "The task contract now includes table, calendar, and note landmarks from the frozen source document. Markdown table separators are treated as layout so complete tables are not falsely rejected.", - "保存产物统一离线复评分:CowAgent 的不完整结果改为失败,Firecrawl 与 Tavily 的完整 Markdown 表格保持通过;不需要重新调用供应商。": "Saved artifacts were consistently rescored offline: CowAgent's incomplete result now fails, while Firecrawl and Tavily retain passes for complete Markdown tables. No provider calls were needed.", - "逐项核验又发现两类跨方法根因:易变网页的整页快照会制造假失败;稳定锚点合同覆盖了最小正文长度,使只含标题和摘要的短输出误判通过。审查代理另有 21 条分歧,来源是忽略显式截断、忽略大小写与空白规范化,或把互不绑定的标题和链接数组按位置配对。": "Item-by-item review found two further cross-method causes: full-page snapshots of mutable pages created false failures, while stable-anchor contracts overrode the minimum body length and let title-only or summary-only output pass. The reviewer also disagreed on 21 results after overlooking explicit truncation, case and whitespace normalization, or the lack of ownership between separate title and URL arrays.", - "82 个易变正文案例改为以独立来源确认的稳定锚点判定,整页快照仅作诊断;正文合同重新执行最小长度门槛。两类修订各有正反向可执行回归,并对保存输出统一离线重算;需新调用的 Crawl4AI 声明范围经认证重跑。Firecrawl 动态任务从当前地区不支持的 actions 改为官方 waitFor 路线,仅认证重跑并复核受影响的 10 条。Lexmount CN 与 Global 的旧方法成绩不再进入当前报告,适配器与接口回归仍保留。": "The 82 mutable body cases now use independently evidenced stable anchors, with full-page snapshots retained only as diagnostics; body contracts again enforce the minimum-length gate. Both corrections have executable positive and negative controls and consistently rescore saved output. The declared Crawl4AI scope was rerun under certification. Firecrawl dynamic tasks moved from actions, which is unavailable in the current account region, to the official waitFor route; only the 10 affected results were certified and reviewed again. Lexmount CN and Global scores are excluded from the current report, while their adapters and interface regressions remain.", - "十二方法全矩阵复核与评分根因修订": "Full-matrix review and root-cause scoring revision for twelve methods", - "十二种方法主矩阵的 1,800 条结果;本轮发布要求机器通过与失败均逐条独立核验,240 条独立同窗测速不在结果审查范围。": "The release requires individual independent review of machine passes and failures across all 1,800 results in the twelve-method primary matrix. The separate 240-call same-window latency cohort is outside result review.", - "同类扫描确认多类系统性判定风险:旧评分实现可能被沿用、明确截断正文可能通过、动态任务排除项未进入可执行合同、复合事实和 Markdown 相对自链接可能误判,挑战页识别与图片资源口径也不一致。": "Family-wide scans identified several systematic verdict risks: stale scorer identities could be reused; explicitly truncated bodies could pass; dynamic-task exclusions were missing from executable contracts; compound facts and Markdown relative autolinks could be misread; and challenge-page detection and image-resource rules were inconsistent.", - "共同根因已在评分身份、正文与任务合同、链接规范化、挑战页判定和资源验收边界中修复;每类修订均绑定正反向可执行回归,并对受影响的保存输出统一重算。图片地址题要求命中声明目标,额外页面图片只作纯净度诊断,不再误作失败依据。": "The shared causes were fixed in scorer identity, body and task contracts, link normalization, challenge-page decisions, and resource-acceptance boundaries. Each revision binds executable positive and negative controls and consistently rescores affected saved outputs. Image-URL tasks require the declared target; additional page images remain a precision diagnostic and no longer cause failure.", - "最终假阳性、假阴性、核验覆盖和未决数量以本轮独立审查收据为准;收据完整绑定前不得发布。接口、版本或请求合同变化的声明范围使用新调用重跑,不写成纯离线重评分。": "Final false-positive, false-negative, review-coverage, and unresolved counts come from this release's independent review receipt; publication is blocked until that receipt is fully bound. Declared scopes affected by an interface, version, or request-contract change are rerun with new calls rather than described as offline-only rescoring.", - "修复评测全链路根因并设置全矩阵核验门禁": "Fix root causes across the evaluation chain and require full-matrix review", - "本轮从任务合同、标准答案、公共评分入口、链接与挑战页判定、资源验收和证据绑定修复系统性误判风险;接口、版本或请求合同变化的范围使用新调用重跑,评分修订则基于原始保存输出离线重算。最终假阳性与假阴性数量由完整独立审查收据生成,不在发布前预写。": "This release addresses systematic verdict risks in task contracts, references, the shared scorer, link and challenge-page decisions, resource acceptance, and evidence binding. Scopes affected by interface, version, or request-contract changes use new calls; scoring revisions rescore the originally saved outputs offline. Final false-positive and false-negative counts are generated from the complete independent review receipt rather than written in advance.", - "全量结果核验": "Full result review", - "上一版只独立审查机器失败,机器通过尚未逐条核验,无法发现被宽松规则放行的系统性假阳性。": "The previous release independently reviewed machine failures only. Machine passes had not been checked individually, leaving systematic false positives from permissive rules undetectable.", - "机器通过与失败统一使用隐藏机器结论的审查包;任务和审查可见证据哈希均未变化的审查继续有效,只重审发生变化的结果。": "Machine passes and failures use the same packets with machine judgments hidden. Prior reviews remain valid only when both the task and reviewer-visible evidence hash are unchanged; only changed results are reviewed again.", - "上一版只独立审查 705 条机器失败,1,095 条机器通过尚未逐条核验,无法发现被宽松规则放行的系统性假阳性。": "The previous release independently reviewed only 705 machine failures; 1,095 machine passes had not been checked individually, so systematic false positives admitted by permissive rules could remain hidden.", - "机器通过与失败统一使用隐藏机器结论的审查包;本轮以主矩阵 1,800 条逐项核验、修订影响项复核和未决清零作为发布门禁。": "Machine passes and failures use the same packets with machine judgments hidden. Publication requires item-by-item review of all 1,800 primary results, renewed review of revision-affected cases, and zero unresolved items.", - "实际核验覆盖、假阳性、假阴性、分歧和未决数量从最终审查收据读取;发布说明不预写尚未完成的数字。同窗测速仍只作时延测量。": "Actual coverage, false-positive, false-negative, disagreement, and unresolved counts come from the final review receipt; release notes do not predeclare unfinished numbers. The same-window cohort remains latency-only.", - "正文、动态与挑战页评分": "Body, dynamic-task, and challenge-page scoring", - "旧评分实现可能在代码更新后继续被报告读取;明确截断正文仍可能凭稀疏锚点通过;易变网页的整页快照会制造假失败;稳定锚点又可能让短摘要绕过最小正文长度。": "A report could keep reading stale scorer output after code changes; explicitly truncated bodies could pass sparse anchors; full snapshots of mutable pages created false failures; and stable anchors could let short summaries bypass the minimum body length.", - "报告构建拒绝旧评分实现;正文任务拒绝明确截断声明;82 个易变正文案例只以独立确认的稳定锚点验收,整页快照改作诊断;正文合同继续执行最小长度门槛。": "Report builds reject stale scorer output; body tasks reject explicit truncation declarations; 82 mutable body cases use only independently evidenced stable anchors for acceptance, with full-page snapshots retained as diagnostics; and body contracts continue to enforce minimum length.", - "新增两组正反向回归并通过公共 score_attempt 与任务判定入口执行;本轮这两类根因共纠正 47 条假阴性和 7 条假阳性,不按方法或单条结果增加特例。": "Two new positive-and-negative regression pairs execute through the shared score_attempt and task-decision paths. Together these causes corrected 47 false negatives and 7 false positives without provider-specific or row-specific exceptions.", - "旧评分实现可能在代码更新后继续被报告读取;明确截断正文仍可能凭稀疏锚点通过;动态任务排除内容只存在于提示文字;复合事实、Markdown 相对自链接与中英文挑战页也存在边界误判。": "Reports could reuse stale scorer identities after a code update; explicitly truncated bodies could pass sparse anchors; dynamic-task exclusions existed only in prompt text; and compound facts, Markdown relative autolinks, and Chinese or English challenge pages had boundary errors.", - "报告构建拒绝旧评分实现;正文任务拒绝明确截断声明;排除项进入机器可执行任务合同;复合事实按语义拆分;链接可见文字和挑战页标题在公共入口统一规范化。": "Report builds reject stale scorer identities; body tasks reject explicit truncation declarations; exclusions enter executable task contracts; compound facts are split semantically; and visible link text and challenge-page headings are normalized in shared code paths.", - "每种缺陷都以相邻的应通过与应失败样本覆盖,并通过公共 score_attempt 与任务判定入口执行;不按方法或单条结果增加特例。": "Each defect is covered by nearby should-pass and should-fail controls executed through the shared score_attempt and task-decision path, without provider-specific or row-specific exceptions.", - "图片资源范围": "Image-resource scope", - "图片地址题一度被拟议成 target-only 输出,可能把已经命中目标、但同时保留页面 Logo 或推荐图的有效抓取误判失败;部分响应式目标别名又需要独立源证据。": "Image-URL tasks were briefly proposed as target-only output, which could fail a valid page fetch that found the target while retaining logos or recommendation images. Some responsive target aliases also require independent source evidence.", - "资源验收只要求命中任务声明的目标或有证据的合法别名;额外页面图片保留为纯净度诊断,不参与二元通过判定。目标清单、别名、来源观察和资源合同均进入哈希绑定。": "Resource acceptance requires a declared target or an evidenced valid alias. Additional page images remain a precision diagnostic and do not affect binary acceptance. Target lists, aliases, source observations, and resource contracts are all hash-bound.", - "避免把页面级抓取强行等同于只返回一个 URL,同时仍拒绝没有命中目标的长文本或大量无关图片;契约变化后旧分数不能发布。": "This avoids treating a page-level fetch as if it had to return only one URL, while still rejecting long text or many unrelated images that never match the target. Stale scores cannot be published after a contract change.", - "防复发与证据修订": "Recurrence prevention and evidence revision", - "评分、参考、数据集和审查收据可分别更新,若生命周期只校验部分字段,旧核验可能被错误沿用;只改最终判定也无法阻止同类错误复发。": "Scores, references, datasets, and review receipts can change independently. Partial lifecycle validation could wrongly reuse an old review, while patching only a final verdict cannot prevent recurrence.", - "每类根因登记正反向可执行回归;参考修订和评分修订分别串联完整输入输出哈希、根因与回归编号,报告构建校验评分实现、任务内容、资源合同和来源证据。": "Each root cause has executable controls in both directions. Reference and scoring revisions separately chain complete input and output hashes, root causes, and regression IDs; report builds validate scorer identity, task content, resource contracts, and source evidence.", - "评分与参考修订不重发 provider 请求;只有接口、版本、输入或请求合同改变的预声明范围才重跑,并保留原调用与新调用的可追溯关系。": "Scoring and reference revisions do not resend provider requests. Only predeclared scopes affected by an interface, version, input, or request-contract change are rerun, with traceability preserved between old and new calls.", - "Firecrawl 动态接口": "Firecrawl dynamic interface", - "旧适配器使用当前账户地区不允许的 actions,10 个动态任务都在服务入口失败,错误地形成 0/10 的能力结论。": "The old adapter used actions, which the current account region does not allow. All 10 dynamic tasks failed at the service boundary, incorrectly producing a 0/10 capability result.", - "改用当前公开接口支持的 waitFor,并通过调用前后参考冻结的认证流程只重跑这 10 个任务;其余 140 条 Firecrawl 结果不变。": "The adapter now uses waitFor, supported by the current public interface. A certified run with references frozen before and after calls reran only these 10 tasks; the other 140 Firecrawl results are unchanged.", - "新结果 6/10 通过;新增 10 条全部由隐藏机器结论的 Codex CLI 盲审,0 分歧、0 未决。剩余 4 条因实际输出不满足任务合同而失败。": "The new results pass 6/10. Codex CLI blindly reviewed all 10 additions with machine judgments hidden, finding 0 disagreements and 0 unresolved items. The remaining 4 fail because their actual output does not satisfy the task contract.", - "报告阅读与耗时": "Report reading and latency", - "全部方法共同成功任务的跨批次耗时表默认展开,占据总榜下方较大篇幅;综合总榜没有同时给出完整 150 次调用的时延分布。": "The cross-batch latency table for tasks passed by every method was expanded by default and occupied substantial space below the ranking; the overall ranking did not show latency over all 150 calls.", - "共同成功任务耗时改为按需展开;综合总榜新增每种方法全部 150 次调用的均值、P50、P95,和通过率使用相同固定分母。": "Shared-success latency is now collapsed on demand. The overall ranking adds mean, P50, and P95 over all 150 calls per method, using the same fixed denominator as coverage.", - "时延列不参与通过率排序,也不选择成功子集;失败调用耗时保留在全调用分布中。": "Latency columns do not affect coverage ordering or select a successful subset; failed-call elapsed time remains in the all-call distribution.", - "请求状态与空输出": "Request status and empty output", - "旧适配器把供应商调用成功但没有可验收正文,与供应商或目标请求失败混成同一种失败,导致请求成功率与任务通过率语义串线。": "The old adapters conflated a successful provider call with no acceptable body and a provider or target request failure, mixing request-success and task-pass semantics.", - "Cloudflare 依据保存的 success、result 与 origin status 信封归一化;Moli 依据 1.1.4 的退出状态和完整响应错误信封归一化。空的成功响应仍是请求成功、任务失败;明确的目标非 2xx 仍是请求失败。": "Cloudflare now normalizes saved success, result, and origin-status envelopes; Moli uses the 1.1.4 exit status and whole-response error envelope. An empty successful response remains request success but task failure, while an explicit target non-2xx remains request failure.", - "14 条状态修正均由哈希绑定的原始证据离线重建,任务判定和分数不变,无 provider 调用。": "All 14 status corrections were reconstructed offline from hash-bound raw evidence. Task verdicts and scores did not change, and no provider was called.", - "动态提取与公开产物": "Dynamic extraction and public artifacts", - "Crawl4AI 动态购物车请求声明了限定提取,但 worker 未传入对应配置并丢失卡片边界;OpenClaw 错误堆栈和 CowAgent 保存提示又会把本机路径带入公开证据。": "The Crawl4AI dynamic-cart request declared bounded extraction, but the worker did not pass the configuration and lost card boundaries. OpenClaw error stacks and CowAgent save notices could also expose host-local paths in public evidence.", - "Crawl4AI worker 传入已声明的提取配置并保留卡片间边界;OpenClaw/DeepSeek 错误面只保留稳定消息,CowAgent 仅删除本机保存路径而保留抓取正文。": "The Crawl4AI worker now passes the declared extraction configuration and preserves card boundaries. OpenClaw and DeepSeek error surfaces retain only stable messages, while CowAgent removes only host-local save paths and keeps fetched text.", - "Crawl4AI 该题认证重跑后通过,方法总分增至 119/150;OpenClaw 15 条和 CowAgent 11 条受影响产物认证重跑,任务总分不变,公开产物不再包含真实主机路径。": "The certified Crawl4AI rerun passes and raises its total to 119/150. Certified reruns covered 15 affected OpenClaw artifacts and 11 CowAgent artifacts without changing their task totals; public artifacts no longer contain real host paths.", - "参考证据一致性": "Reference-evidence consistency", - "四个完整正文样本的来源说明曾错误指向其他网站;NOAA 样本已有当日独立 200 响应、main 区域和完整正文哈希,却保留了“全文不可用”的旧备注。": "Four complete-body samples had source descriptions pointing to unrelated sites. The NOAA sample had a same-day independent 200 response, main-region extraction, and complete-body hash but retained a stale note saying the full text was unavailable.", - "完整正文来源必须与题目 URL 同站;NOAA 元数据改为明确记录完整参考可用,稳定锚点继续决定二元验收,全文快照只提供诊断指标。新增数据集门禁拒绝有效全文与不可用声明并存。": "Complete-body evidence must use the task URL's site. NOAA metadata now explicitly records the complete reference as available; stable anchors still drive binary acceptance and the full snapshot supplies diagnostic metrics. A dataset guard rejects valid full text paired with an unavailable declaration.", - "来源与备注修正不改变 provider 输出或任务分数;相关结果重新绑定并独立复核。": "Source and note corrections do not change provider output or task scores; affected results were rebound and independently reviewed.", - "异常根因门禁": "Anomaly root-cause gate", - "仅比较标准答案与最终判定无法主动发现空输出、跨站相同错误、集中零分、接口误用、参考冲突和公开产物泄漏;按批次序号缓存又会在小修后错配审查结果。": "Comparing only a reference and final verdict cannot proactively find empty output, repeated cross-site errors, concentrated zero scores, interface misuse, reference conflicts, or public-artifact leakage. Caching by batch number also misaligns reviews after small revisions.", - "逐条盲审之后,对确定性异常信号执行七层调查;异常缓存按精确 attempt 范围而不是批次位置复用,范围变化时只重审变化组。": "After row-level blind review, deterministic anomaly signals receive a seven-layer investigation. Anomaly caches are reused by exact attempt scope rather than batch position, so only changed groups are reviewed again.", - "请求语义、公开产物与异常门禁复核": "Request semantics, public artifacts, and anomaly-gate review", - "发现请求成功与正文合格被混为一谈、Crawl4AI 动态卡片边界丢失、OpenClaw/CowAgent 公开产物含本机路径,以及 NOAA 已有完整独立参考却保留“全文不可用”旧备注。": "The review found conflated request success and acceptable content, lost Crawl4AI dynamic-card boundaries, host-local paths in OpenClaw and CowAgent public artifacts, and a stale NOAA note declaring full text unavailable despite a complete independent reference.", - "请求状态按供应商保存信封与退出状态统一归一化;Crawl4AI 使用已声明的限定提取并保留卡片边界;公开产物在适配器边界删除本机路径;NOAA 参考元数据重新绑定独立 200 响应、main 区域和正文哈希。只对接口或产物身份受影响的范围认证重跑,其余修正离线完成。异常审查缓存改按精确结果范围复用,不再依赖易漂移的批次序号。": "Request status is normalized from saved provider envelopes and exit status. Crawl4AI uses the declared bounded extraction and preserves card boundaries. Host-local paths are removed at the adapter boundary. NOAA reference metadata is rebound to the independent 200 response, main region, and body hash. Only scopes with changed interface or artifact identity were rerun; other corrections were completed offline. Anomaly-review caches now reuse exact result scopes rather than unstable batch numbers.", - "Moli:普通正文用 Markdown,挑战题用 JSON 返回最终 DOM、状态和跳转诊断;动态等待与正文范围分离。DOCX 在同一次 CLI 运行中二次 fetch 原始字节;不调用模型。": "Moli: ordinary body tasks use Markdown, while challenge tasks use JSON to return the final DOM, status, and redirect diagnostics. Dynamic waiting is separate from body scope. DOCX performs a second fetch for original bytes within the same CLI run; no model is called.", - "动态 / 资源页 / DOCX": "Dynamic / resource pages / DOCX", - "资源输出契约:只允许在答案图片字段中返回登记的目标、别名或匹配规则;额外图片会使任务未通过。": "Resource output contract: answer-bearing image fields may contain only registered targets, aliases, or matching patterns; additional images fail the task.", - "资源召回率 / 纯净度": "Resource recall / precision", - "非目标资源": "Unexpected resources", - "候选资源地址": "Candidate resource URL", - "答案字段来源": "Answer-field source", - "范围判定": "Scope decision", - "目标或允许变体": "Target or allowed variant", - "已找到目标资源,但答案字段还返回了非目标资源:": "The target resource was found, but answer-bearing fields also returned unexpected resources: ", - ";同时返回非目标资源:": "; also returned unexpected resources: ", - "未命中登记的目标资源。": "No registered target resource was found.", - "返回非目标资源": "Returned unexpected resources", - "目标资源已命中,但答案字段还返回了非目标资源:": "The target resource was found, but answer-bearing fields also returned unexpected resources: ", - "打开完整图片": "Open full-size image", - "下载图片": "Download image", - "从输入到交付共 2.44 秒,包含流程开销。本题预先声明为 PDF 文档,未测试未知链接的自动类型识别。": "Input-to-delivery time is 2.44 seconds, including processing overhead. This task is declared as a PDF document in advance; automatic type detection for an unknown URL was not tested.", - "排除:Just a moment... / Prove your humanity / Access Denied;不得返回安全验证或拒绝访问页": "Exclude: Just a moment... / Prove your humanity / Access Denied; must not return a security-verification or access-denied page", - "评测集与判定规则": "Dataset and acceptance rules", - "名称至少 2 个字符且含字母;以 http(s) 开头的名称,以及“详情”“Read more”等登记的入口标签会被剔除。": "Names need at least 2 characters and a letter. Names starting with http(s), and registered link labels such as 'Details' and 'Read more', are excluded.", - "文章目录": "Article contents", - "名称至少 2 个字符且含字母;URL、价格及通用占位标签不算名称。": "Names need at least 2 characters and a letter; URLs, prices, and generic placeholder labels do not count as names.", - "按域名与路径去重;查询参数变化不增加记录数。": "Deduplicate by host and path; query-parameter changes do not increase the record count.", - "正文 0 字符;必要文字命中 0/3。": "Body: 0 characters; required text matched: 0/3.", - "完整记录 0 条;稳定文字命中 0/4。": "Complete records: 0; stable text matched: 0/4.", - "目标图片地址命中 0/1。": "Target image URL matched: 0/1.", - "实际交付 0 个原始文件;这里只返回了 Markdown 图片链接。": "Original files delivered: 0; only a Markdown image link was returned here.", - "与上方登记的完整 URL 匹配": "Match the complete URL registered above", - "规范化后相同": "Identical after normalization", - "PDF 是一个具体原因:Moli 能先交回文件字节,再由本地 pypdf 提取正文。有六道 PDF 题只被 Moli+pypdf 路线完成,其他四款的本轮交付路线未通过。这个成绩比较的是浏览器及其内容处理路径,包含 PDF 解析所用时间。": "PDFs explain part of the difference: Moli returns file bytes, then local pypdf extracts the text. Six PDF tasks are completed only by the Moli + pypdf path; the other four browsers' tested paths fail them. The comparison includes the browser and its content-processing path, including PDF parsing time.", - "每条:非空名称 + 本站详情链接": "Each record: nonempty name + detail link on this site", - "每条:非空名称+本站详情链接": "Each record: nonempty name + detail link on this site", - "域名:": "Hosts: ", - "详情路径规则:": "Detail-path rule: ", - "平均耗时": "Mean latency", - "全部耗时相加,再除以任务数": "Sum all latencies and divide by the task count", - "P50 · 中位数": "P50 · median", - "一半任务的等待不超过这个值": "Half the tasks wait no longer than this value", - "P95 · 较慢一端": "P95 · slower end", - "本组仅 8 题,按报告算法等于最慢一题": "With only 8 tasks, the report's calculation gives the slowest task", - "文件长度": "File length", - "案例原始记录:": "Original case records: ", - "左右滑动查看完整判定。": "Scroll horizontally to view all verdict details.", - "排除:": "Exclude: ", - "替换字符数 ≥20 且占比 ≥2% 时拒绝": "Reject when replacement characters number ≥20 and make up ≥2%", - "PNG 文件头:89504e470d0a1a0a": "PNG header: 89504e470d0a1a0a", - "文件长度的通用门槛是 32 字节。SHA-256 是按文件字节计算的指纹,用来核对是否同一份文件;不评价图片识别或媒体语义。": "The general file-length threshold is 32 bytes. SHA-256 is a fingerprint calculated from the file's bytes to check file identity; it does not assess image recognition or media semantics.", - "网页、PDF、列表、资源地址和原文件采用可切换面板,逐项核对独立参考与真实返回;保留原始 PNG,关键数字直接排版。": "Selectable panels for web text, PDFs, lists, resource URLs, and original files compare independent references with actual outputs check by check. The original PNG is retained, with key figures presented directly as text.", - "明确结构检查不核验商品名称事实、文件长度门槛为 32 字节,以及正文规则经过统一离线修订。": "Clarify that structural checks do not verify product-name facts, the file-length threshold is 32 bytes, and body rules received shared offline revisions.", - "五款浏览器 · 150 道任务": "Five browsers · 150 tasks", - "Moli 读取网页与文件,": "How well does Moli", - "实际表现如何?": "read pages and files?", - "任务通过率 · 五款中最高": "Task pass rate · highest of the five", - "共 150 道,通过 135 道,未通过 15 道": "150 tasks: 135 passed, 15 failed", - "相比 Chromium": "Compared with Chromium", - "多完成 10 道": "10 more tasks completed", - "净差来自文档与媒体;网页类总数持平": "Net difference from documents and media; web totals tied", - "相同任务的速度": "Latency on the same tasks", - "均值、P50 最低": "Lowest mean and P50", - "8 道共同通过的网页;P95 最低是 Lightpanda": "8 shared web successes; Lightpanda has the lowest P95", - "阅读完整评测报告 →": "Read the full benchmark report →", - "查看全部任务、评分规则与浏览器原始返回": "Browse every task, scoring rule, and original browser output", - "测什么": "Scope", - "结果与速度": "Results and latency", - "五个真实案例": "Five real examples", - "浏览器与配置": "Browsers and configurations", - "失败与改进": "Failures and improvements", - "评测范围": "Benchmark scope", - "这次只测 Web Fetch:给定 URL,取回所需内容": "This benchmark covers Web Fetch: retrieve the required content from a URL", - "让 AI 总结一篇论文,首先得把论文交给它。地址可能是一张网页,也可能直接指向 PDF、图片或视频文件。Web Fetch 负责读取这个地址,并交回任务要求的正文、列表、资源链接或原文件。": "Before AI can summarize a paper, it needs the paper. The address may point to a page or directly to a PDF, image, or video file. Web Fetch reads that address and returns the required body text, list, resource link, or original file.", - "本次评测:Web Fetch": "This evaluation: Web Fetch", - "“读取这篇论文的正文。”": "“Read the body of this paper.”", - "给定 URL": "Given URL", - "读取与提取": "Read and extract", - "验收返回内容": "Check returned content", - "另行评测:浏览器自动化": "Separate evaluation: browser automation", - "“搜索商品,筛选规格,再加入购物车。”": "“Search for a product, filter specifications, and add it to the cart.”", - "观察页面": "Observe page", - "点击或填写": "Click or fill in", - "继续下一步": "Continue to the next step", - "本轮 150 道题全部是 Web Fetch。我们检查内容是否拿对、拿全,以及从输入 URL 到交付结果用了多久。AI 能否理解论文、浏览器能否连续操作网站,不计入这次成绩。": "All 150 tasks in this run are Web Fetch tasks. We check whether the right content was retrieved, whether it is complete, and how long it took from input URL to output. AI's understanding of the paper and the browser's ability to perform sequences of website actions are not scored here.", - "150 道任务,Moli 通过 135 道": "150 tasks, with Moli passing 135", - "Moli 的任务通过率为 90.0%,高于 Chromium 的 83.3%、Lightpanda 的 79.3%、Obscura 的 78.0% 和 Kitesurf 的 74.7%。这里的“通过”是按每题要求取得合格结果,失败和超时仍计入总题数。": "Moli's task pass rate is 90.0%, above Chromium's 83.3%, Lightpanda's 79.3%, Obscura's 78.0%, and Kitesurf's 74.7%. Passing means returning an acceptable result under the task's requirements. Errors and timeouts remain in the total task count.", - "任务通过数 / 总任务数": "Tasks passed / total tasks", - "每款浏览器均执行 150 道题,每题等权。该指标是任务完成率,不是对所有返回文字的事实准确率。": "Each browser ran 150 equally weighted tasks. This measures task completion, not the factual accuracy of all returned text.", - "多完成的 10 道题,来自哪里?": "Where do the 10 additional completed tasks come from?", - "与 Chromium 相比,Moli 在网页类的总通过数相同;到了文档与媒体,两者拉开了差距。": "Moli and Chromium pass the same total number of web tasks. The gap appears in documents and media.", - "PDF 是一个具体原因:Moli 能先交回文件字节,再由本地 pypdf 提取正文。六道 PDF 任务中,这条路线都拿到了文字,其他四款的本轮页面转文本配置没有完成。这个成绩比较的是浏览器及其内容处理路径,包含 PDF 解析所用时间。": "PDFs explain part of the difference: Moli returns file bytes, then local pypdf extracts the text. This path retrieves text on six PDF tasks that the other four browsers' tested page-to-text configurations do not complete. The comparison includes the browser and its content-processing path, including PDF parsing time.", - "网页类总数相同,也不意味着表现完全一样。Moli 的普通网页是 97/100,Chromium 是 99/100;热门站点为 6/10 对 4/10,两道题的得失恰好抵消。下面的案例可以看到,这些差异具体发生在哪一步。": "Equal web totals do not mean identical performance. Moli scores 97/100 on general pages and Chromium 99/100; popular-site scores are 6/10 versus 4/10, offsetting the two-task difference. The examples below show where these differences occur.", - "速度看三个数:平均耗时、P50 和 P95": "Three latency measures: mean, P50, and P95", - "对同一个网页,先确认大家都完成了任务,再比较等待时间。完整报告从预先选定的 20 道普通网页中,取出所有受测配置都通过的 8 道;以下五款浏览器比较的都是这 8 道,避免把快速失败当成速度优势。": "For a given page, confirm that every browser completed the task before comparing latency. From 20 preselected general pages, the full report uses the 8 passed by every tested configuration. All five browsers below are compared on those same 8 tasks, so a quick failure cannot count as a speed advantage.", - "同样 8 道网页任务的耗时 · 越低越快": "Latency on the same 8 web tasks · lower is faster", - "绿色表示该项最低值。均值由原始耗时求算术平均;P50、P95 沿用完整报告口径。包含内容提取与后处理,远端服务还包含网络往返。": "Green marks the lowest value in each column. The mean is the arithmetic average of recorded latencies; P50 and P95 follow the full report's definitions. Content extraction and post-processing are included, as are network round trips for the remote service.", - "Moli 在平均耗时和 P50 上领先这四款浏览器,但并没有包揽三项第一:Lightpanda 的 P95 为 6.03 秒,低于 Moli 的 6.55 秒。P50 就是中位数;把三项一起看,既能看到通常要等多久,也能看到慢任务的影响。": "Moli leads these four browsers on mean latency and P50, but not all three measures: Lightpanda's P95 is 6.03 seconds, below Moli's 6.55 seconds. P50 is the median. Together, the three measures show typical waits and the effect of slower tasks.", - "为什么是 8 道?这组速度数据能说明什么?": "Why 8 tasks, and what do these latency figures establish?", - "这组数据适合观察同一批任务中的速度差异,不能估计长期服务延迟或吞吐。样本只有 8 道,按当前 P95 算法,P95 实际等于其中最慢的一道。": "These data show latency differences within the same task set, but cannot estimate long-term service latency or throughput. With only 8 tasks, the current P95 calculation makes P95 equal to the latency of the slowest task.", - "任务怎样判分": "How tasks are scored", - "标准答案与真实输出,放在一起看": "Reference answers beside actual outputs", - "下面五个案例分别展示网页正文、PDF、列表、资源地址和原文件怎样验收。左边是独立参考或预先登记的要求,右边是本轮真实返回,下面逐项标出通过与失败。长内容只截取对应片段,并标明范围。": "Five examples show acceptance for page text, PDFs, lists, resource URLs, and original files. The left side contains an independent reference or registered requirements; the right side shows actual output from this run, with pass and fail checks below. Long content uses corresponding excerpts with their scope stated.", - "每题只记通过或失败": "Each task is pass or fail", - "必需检查全部满足": "All required checks must pass", - "未通过项保留在分母": "Failed tasks stay in the denominator", - "评测集、阈值与独立复核": "Dataset, thresholds, and independent review", - "五个案例是五种阅读入口,不是五个统计分组。完整评测集分为普通网页、动态网页、热门站点、文档、图片、视频六组。图片和视频可能要求资源地址或原文件,PDF 正文则使用正文覆盖规则。": "The five examples are ways to explore the results, not five reporting groups. The full dataset has six groups: general pages, dynamic pages, popular sites, documents, images, and videos. Image and video tasks can require URLs or original files; PDF text uses body-coverage rules.", - "参考内容来自独立取得的页面或文件;列表使用独立确认的结构要求,不固定每天变化的商品名单。正文阈值是工程验收下限,未通过敏感性实验确定为最优值。评分曾统一修订并离线重算,当前成绩采用最终复核口径。": "References come from independently retrieved pages or files. Lists use independently confirmed structural requirements without freezing a changing daily catalog. Body thresholds are engineering acceptance floors, not values shown optimal by sensitivity experiments. Scoring rules were revised consistently and results rescored offline; current scores use the final reviewed rules.", - "相同任务,五条内容获取路径": "The same tasks, five content-retrieval paths", - "定位与运行位置": "Purpose and execution location", - "本轮交付路径": "Output path in this run", - "面向 AI Agent 的 Rust 浏览器;本地": "Rust browser for AI agents; local", - "原生 Markdown;文档与媒体可取原始响应;PDF 使用 pypdf": "Native Markdown; raw responses for documents and media; pypdf for PDFs", - "通用开源浏览器;本地无头模式": "General-purpose open-source browser; local headless mode", - "页面 DOM 经 node-html-markdown 2.0.0 转成 Markdown": "Page DOM converted to Markdown with node-html-markdown 2.0.0", - "Zig 开发的自动化浏览器;本地": "Automation browser written in Zig; local", - "采用 V8 的 Rust 无头浏览器;本地": "Rust headless browser using V8; local", - "标准模式的原生 Markdown,未启用 stealth 或备用引擎": "Native Markdown in standard mode; no stealth mode or alternative engine", - "Cloudflare 面向 Agent 的浏览器;远端": "Cloudflare browser for agents; remote", - "通过 Browser Run 接口提交 URL、返回 Markdown": "Submit URL through Browser Run; receive Markdown", - "这是五款浏览器在上述配置下的任务结果,不是单独比较内核。改变下载器、提取器或等待策略,需要重新测试。": "These are task results for the five browsers in the stated configurations, not an isolated engine comparison. Changing a downloader, extractor, or waiting strategy requires another test.", - "几道单独通过的任务,也说明了总榜没有涵盖的差异。Lightpanda 完成了其他四款未通过的 IMDb 题;Kitesurf 完成了 YouTube 题。Obscura、Lightpanda 和 Chromium 找到了 Blender 下载包的可解析入口,而 Moli 虽返回目标文件名,却没有把它交成合格链接。": "Individual successes show differences the overall ranking misses. Lightpanda passes IMDb, where the other four fail; Kitesurf passes YouTube. Obscura, Lightpanda, and Chromium find a parseable Blender package link. Moli returns the target filename but not an acceptable link.", - "因此,如果你的 Agent 既读网页,也读 PDF、取媒体原文件,Moli 当前的覆盖优势值得优先测试。若主要处理长篇 HTML 正文,Chromium 仍是重要参照;云端部署、并发和成本则需要另行评估,本轮没有测量。": "For agents reading pages and PDFs and retrieving original media files, Moli's current coverage advantage makes it worth testing early. For long HTML text, Chromium remains an important reference. Cloud deployment, concurrency, and cost need separate evaluation; they were not measured here.", - "通过率领先之后,还要补哪些内容?": "Beyond the leading pass rate, what still needs work?", - "长网页:需要拿到正文,而不是验证页": "Long pages: retrieve the body, not a verification page", - "即使任务通过,正文找回多少也值得单独看。98 道有完整可比参考的普通网页中,Chromium 的 Recall 与 F1 均高于 Moli。这也是本文使用“任务通过率最高”,而不把它扩大成“所有内容准确率最高”的原因。": "Even when a task passes, body recovery deserves a separate look. Across 98 general pages with complete, comparable references, Chromium has higher recall and F1 than Moli. That is why this article claims the highest task pass rate, not the highest accuracy for all content.", - "动态内容:延迟出现的列表和 iframe": "Dynamic content: delayed lists and iframes", - "五款浏览器的动态任务都是 8/10。共同未通过的两道题,暴露了两种常见的读取缺口。": "All five browsers score 8/10 on dynamic tasks. Their two shared failures expose two common retrieval gaps.", - "内容晚 3 秒出现": "Content appears after 3 seconds", - "3 秒:目标列表": "3 seconds: target list", - "Moli 交回了框架,没有目标列表。需要检查等待条件,以及最终是否取到了完整记录。": "Moli returns the shell without the target list. Waiting conditions and retrieval of complete records need checking.", - "目录在 iframe 里": "Catalog inside an iframe", - "下一轮": "Next round", - "扩大样本,再验证优势能否保持": "Expand the sample and test whether the advantages hold", - "下一轮将扩大站点与文档样本,为热门站点安排固定次数的重复运行,并重点回归延迟列表、iframe、DOCX 和嵌入式查看器。速度也需要更多共同成功样本,才能更稳妥地观察 P95。": "The next round will expand site and document samples, schedule fixed repetition counts for popular sites, and prioritize regression tests for delayed lists, iframes, DOCX, and embedded viewers. Latency also needs more shared successes for a more reliable view of P95.", - "随后还会用独立的浏览器自动化评测,比较搜索、点击、翻页和填写等连续操作,检验 Agent 能否完成整段网页任务。": "A separate browser-automation evaluation will then compare sequences of searches, clicks, pagination, and form inputs to test whether an agent completes the whole web task.", - "查看完整报告与全部真实输出 →": "View the full report and every actual output →", - "按任务或浏览器查看结果,也可核对评分与调用记录": "Browse results by task or browser, and inspect scoring and call records", - "Example Domain 网页": "Example Domain page", - "Attention Is All You Need 论文": "Attention Is All You Need paper", - "Anthropologie 蜡烛列表": "Anthropologie candle list", - "Pexels 图片下载地址": "Pexels image download URL", - "httpbin PNG 文件": "httpbin PNG file", - "图片地址": "Image URL", - "完整输出与逐项判定": "Complete output and item-level verdicts", - "独立参考正文 · 摘要节选": "Independent reference text · abstract excerpt", - "两侧均为节选;评分使用完整正文。Moli 取得 PDF 后,由本地 pypdf 在同一预算内提取文字。": "Both sides show excerpts; scoring uses the complete text. After Moli retrieves the PDF, local pypdf extracts text within the same budget.", - "独立参考正文 · 全文": "Independent reference text · complete body", - "参考以正文保存,实际输出保留 Markdown 标记;按文字内容验收。": "The reference is saved as body text; actual output retains Markdown markup. Acceptance checks the textual content.", - "必要文字": "Required text", - "正文分区覆盖": "Body-region coverage", - "排除错误页": "Exclude error pages", - "不出现登记的拦截或错误文字": "No registered blocking or error text", - "未命中排除项": "No excluded text matched", - "文字解码": "Text decoding", - "未触发已登记的解码损坏判据": "Registered decoding-damage criteria not triggered", - "独立观察的稳定文字": "Independently observed stable text", - "记录要求": "Record requirements", - "原始 Markdown 中三条商品记录节选": "Three product-record excerpts from the original Markdown", - "稳定文字": "Stable text", - "完整记录": "Complete records", - "唯一记录": "Unique records", - "商品名称、价格和排序不作为固定答案;本题不核验名称的事实正确性或信息时效。右侧节选不是完整列表。": "Product names, prices, and ordering are not fixed answers. This task does not verify the factual correctness of names or information freshness. The right-hand excerpt is not the complete list.", - "独立确认的目标资源": "Independently confirmed target resource", - "目标图片地址": "Target image URL", - "本题的资源匹配规则忽略 URL 查询参数,因此实际下载链接与登记目标匹配;这不是对任意下载地址的通用等价判断。本题不要求下载图片,也不检查推荐图片的数量。": "This task's resource-matching rules ignore URL query parameters, so the actual download link matches the registered target. This is not a general equivalence rule for arbitrary download URLs. The task neither requires downloading the image nor checks the number of recommended images.", - "独立目标文件": "Independent target file", - "目标 SHA-256": "Target SHA-256", - "实际 SHA-256": "Actual SHA-256", - "文件签名": "File signature", - "至少 32 字节;独立目标为 8,090 字节": "At least 32 bytes; independent target is 8,090 bytes", - "文件一致性": "File identity", - "匹配独立目标 SHA-256": "Match the independent target SHA-256", - "一致": "Identical", - "不一致": "Different", - "文件长度的通用门槛是 32 字节;目标哈希进一步确认该文件逐字节一致。不评价图片识别或媒体语义。": "The general file-length threshold is 32 bytes; the target hash further confirms byte-for-byte identity. Image recognition and media semantics are not evaluated.", - "Chromium · 本轮对照输出": "Chromium · comparison output from this run", - "空输出 · 0 字符": "Empty output · 0 characters", - "以上为前 400 字符节选。": "Excerpt of the first 400 characters.", - "标准答案与要求": "Reference answer and requirements", - "本次真实输出": "Actual output from this run", - "检查项": "Check", - "验收要求": "Acceptance requirement", - "Moli 实际结果": "Moli's actual result", - "本题通过。": "This task passed.", - "本题未通过。": "This task failed.", - "正文和列表结构任务还会检查文本是否有明显的解码损坏:替代字符“�”同时达到 20 个、占输出至少 2%,即判失败。这能识别一部分乱码,不能代替对文字含义的核验。下面两个正文样本均未触发这一检查。": "Body-text and structured-list tasks also check for obvious decoding damage: the task fails if it contains at least 20 replacement characters, '�', and they make up at least 2% of the output. This detects some garbled text, but does not verify meaning. Neither body-text sample below triggers this check.", - "链接按域名和路径识别同一商品,查询参数不同不会多算一条。空名称、纯数字名称,以及“详情”“Read more”这类入口标签不算商品名称;只接受 HTTP 或 HTTPS 链接,且链接不能夹带用户名或密码。": "Links identify the same product by host and path; different query parameters do not create an additional record. Empty or numeric-only names, and link labels such as 'Details' or 'Read more', do not count as product names. Only HTTP or HTTPS links are accepted, and they must not contain a username or password.", - "最低长度(字节)": "Minimum length (bytes)", - "直接 PNG:用文件指纹核对实际字节": "Direct PNG: verify the actual bytes with a file fingerprint", - "本轮输入统一是 URL。有的地址指向网页,其中可能嵌有图片、视频或文档;有的直接指向 PDF、PNG 或视频文件,点击地址就能取得文件响应。评测会按题目要求读取正文、定位资源地址或取得原文件。它关心从 URL 到可用内容这一段:该有的正文是否齐全,列表是否包含要求的字段,返回的文件是否正确,以及这一过程花了多久。模型拿到内容后能否推理正确、浏览器能否完成连续网页操作,都需要另外评测。": "Every input in this run is a URL. Some point to pages that may embed images, videos, or documents; others point directly to PDFs, PNGs, or videos and return a file response. Depending on the task, the benchmark reads body text, locates a resource URL, or retrieves the original file. It checks whether the required text is complete, list fields are present, files are correct, and how long retrieval takes. Correct reasoning from that content and completing sequences of web interactions need separate evaluations.", - "在相同成功任务上,Moli 的中位耗时在本文五款浏览器中最低": "On the same successful tasks, Moli has the lowest median latency among the five browsers here", - "网页或 PDF 地址": "Web or PDF URL", - "列表页 URL": "List-page URL", - "资源详情页 URL": "Resource-detail URL", - "文件直链 URL": "Direct-file URL", - "Moli+pypdf 在六道其他四款浏览器都未完成的 PDF 任务中取得了正文,构成文档组的六题差距。直接 DOCX 和两个嵌入式查看器仍未通过;普通 PDF 的这条处理路线尚未覆盖这些输入。": "Moli + pypdf retrieves the text on six PDF tasks that all four other browsers fail, accounting for the document group's six-task gap. The direct DOCX task and two embedded viewers still fail; this ordinary-PDF path does not yet cover those inputs.", - "Moli 在这道图片题胜出,但在 Blender 官方视频下载包题中只返回了目标文件名,没有形成可解析的下载地址;Chromium、Lightpanda 和 Obscura 则交付了指定的 1080p、30fps、normal 版本入口。资源发现仍要按具体目标逐项测试。": "Moli wins this image task, but returns only the target filename in the official Blender video-package task, without a parseable download URL. Chromium, Lightpanda, and Obscura deliver the specified 1080p, 30fps, normal-version link. Resource discovery still needs target-by-target testing.", - "Lightpanda 不是 Chromium 分支。本轮普通网页完成 92/100,三篇 Moli 未完成的 W3C 标准都取到了正文,IMDb 任务也只有它通过。共同成功速度子集中,P50 为 3.49 秒,P95 在本文五款浏览器中最低;文档 1/10、热门站点 4/10 则显示这条路线在混合输入中的缺口。": "Lightpanda is not a Chromium fork. It completes 92/100 general pages, retrieves all three W3C standards that Moli missed, and is the only browser here to pass IMDb. In the shared-success latency subset, its P50 is 3.49 seconds and its P95 is lowest among the five browsers. Scores of 1/10 for documents and 4/10 for popular sites show gaps in this mixed-input path.", - "Obscura 的标准模式完成 90/100 个普通网页和 5/10 个热门站点任务,也找到了 Moli 未完成的 Blender 下载包入口。相同成功任务上的 P50 为 11.69 秒,是本文五款浏览器中最高的。这个结果对应当前模式与端到端调用,不能外推到未启用的其他模式。": "Obscura's standard mode completes 90/100 general pages and 5/10 popular-site tasks, and finds the Blender package link Moli missed. Its P50 on the same successful tasks is 11.69 seconds, the highest of the five browsers here. This describes the current mode and end-to-end call, not other modes that were not enabled.", - "动态网页:五款浏览器都停在 8/10": "Dynamic pages: all five browsers score 8/10", - "窄屏可左右滑动查看完整表格。": "On narrow screens, scroll horizontally to view the full table.", - "0 秒:页面框架": "0 seconds: page shell", - "3 秒:目标列表出现": "3 seconds: target list appears", - "外围页面": "Outer page", - "iframe 子页面:目标目录": "iframe content: target catalog", - "验收解释": "Acceptance explained", - "五类任务原先只有概念流程,难以核对具体通过原因。": "The five task types previously had only conceptual flows, making the reasons for passing difficult to check.", - "补充网页、PDF、列表、资源地址和原文件的真实样例、逐项门槛及人为删改对照;明确四类交付物、五个样例与六个统计分组。": "Add real web, PDF, list, resource-URL, and original-file examples, with itemized thresholds and edited controls. Distinguish four output types, five examples, and six reporting groups.", - "新增对照不计入主榜;披露结构检查不核验商品名称事实、文件长度门槛为32字节,以及正文规则经过统一离线修订。": "New controls do not count toward the main ranking. Disclose that structural checks do not verify product-name facts, the file-length threshold is thirty-two bytes, and body rules received shared offline revisions.", - "当 AI 助手需要读取网页、论文和媒体文件,浏览器能否把所需内容完整交回来?我们用同一组任务,对比了 Moli、Chromium、Lightpanda、Obscura 和 Kitesurf。": "When an AI assistant needs to read pages, papers, and media files, can its browser return all the required content? We compared Moli, Chromium, Lightpanda, Obscura, and Kitesurf on the same tasks.", - "本轮结果总览。图示概括任务交付与实测差异,具体标准答案和判定过程见下文。": "An overview of this run. The illustration summarizes required outputs and measured differences; the reference answers and acceptance checks are explained below.", - "五个验收样例": "Five acceptance examples", - "“读完这篇论文,整理主要结论”和“打开网站,搜索产品,再把它加入购物车”,都会用到浏览器,但对 Agent(能调用工具执行任务的 AI 助手)来说是两种工作。前者已经知道要读取的地址,需要取得内容;后者需要根据页面变化决定下一步,反复执行搜索、点击、填写和检查。": "Both 'read this paper and summarize its findings' and 'open the site, search for a product, and add it to the cart' use a browser. But they ask different things of an agent: an AI assistant that can call tools to carry out tasks. The first starts with a known address and needs its content. The second requires repeated searches, clicks, form inputs, and checks, with each action chosen from the page's current state.", - "02 / 怎样判定完成": "02 / How completion is assessed", - "用五个真实样本,把验收过程展开": "Five real examples, with the acceptance checks explained", - "同样是交给浏览器一个 URL,任务可能要求一篇正文、几条商品记录、一条下载地址,也可能直接要文件。判断是否完成,必须先明确要交回什么。下面用五个样本解释这四类交付物,网页正文和 PDF 正文因处理路线不同分开举例。实际数据按六个分组统计:普通网页 100 题,动态网页、热门站点、文档、图片、视频各 10 题。图片和视频组都可能要求资源地址或原文件,PDF 则沿用正文验收。": "A URL can require body text, product records, a download link, or the file itself. Acceptance starts by defining that output. Five examples below explain these four output types; web and PDF text have separate examples because their processing paths differ. Results use six reporting groups: general pages with 100 tasks, and dynamic pages, popular sites, documents, images, and videos with 10 each. Image and video tasks can require URLs or files, while PDFs use body-text acceptance.", - "从调用开始,到可验收结果形成": "From the start of the call to an assessable output", - "网页 / PDF": "Web / PDF", - "页面转文本,或取文件后解析": "Convert page to text, or retrieve and parse a file", - "正文内容": "Body text", - "必需文字+长度+分区覆盖": "Required text + length + region coverage", - "提取具名条目与详情入口": "Extract named entries and detail links", - "结构记录": "Structured records", - "字段+数量+去重+稳定文字": "Fields + count + uniqueness + stable text", - "图片 / 视频详情": "Image / video details", - "在返回内容中找到指定资源": "Find the required resource in the output", - "资源地址": "Resource URL", - "与预登记目标 URL 匹配": "Match the registered target URL", - "直接文件": "Direct file", - "取得原始响应字节": "Retrieve raw response bytes", - "格式签名+长度+文件指纹": "Format signature + length + file fingerprint", - "各行末尾是不同任务的验收出口。返回很多文字不能替代目标 URL,返回图片说明也不能替代图片文件。": "Each row ends with its task's acceptance checks. A large amount of text cannot replace the target URL, and an image description cannot replace the image file.", - "样本 1 · 网页正文": "Example 1 · Web body text", - "Example Domain:有标题,还不够": "Example Domain: the title is not enough", - "输入:": "Input: ", - "这道题要取得页面正文。标准答案来自独立 HTTP 获取的页面,包含标题、用途说明和更多信息入口。本轮 Moli 与 Chromium 都返回了下面这份内容,各 167 个字符。": "This task requires the page body. The reference comes from an independent HTTP retrieval and includes the title, purpose, and further-information link. Both Moli and Chromium returned the content below, each with 167 characters.", - "两款方案的实际返回 · 完整短样本": "Actual output from both methods · complete short sample", - "本题以下检查全部满足,才判通过。": "Every check below must pass for this task to pass.", - "检查什么": "Check", - "登记的要求": "Registered requirement", - "实际结果": "Observed result", - "标题与说明": "Title and explanation", - "标题命中 Example Domain;用途命中 documentation examples 或 illustrative examples": "Title matches Example Domain; purpose matches documentation examples or illustrative examples", - "两组均命中": "Both groups matched", - "更多信息入口": "Further-information link", - "iana.org/domains/example、More information、Learn more 三者命中任一项": "Match any of iana.org/domains/example, More information, or Learn more", - "命中 Learn more": "Matched Learn more", - "输出长度": "Output length", - "正文区域": "Body region", - "唯一参考分区至少覆盖 80%": "At least 80% coverage of the single reference region", - "102 / 102 个片段,覆盖 100%": "102 / 102 shingles, covering 100%", - "错误提示": "Error text", - "不得包含 Access denied、Attention Required! | Cloudflare、CAPTCHA": "Must not contain Access denied, Attention Required! | Cloudflare, or CAPTCHA", - "没有命中": "None matched", - "这里的三组文字不是必须逐字一致:每组接受题目登记的等价表达,但三组都要满足。分区检查则先规范大小写、空白和 Markdown 格式,再比较连续的六字符片段;这个覆盖比例参与任务判定。后面的 Word Precision、Recall 按词计算,是另列的全文质量诊断,不用于代替这项分区要求。两者的计算单位不同,也都不判断语义真伪。": "The three text groups do not require identical wording: each accepts the task's registered alternatives, but all three must match. Region checks normalize case, whitespace, and Markdown, then compare consecutive six-character shingles. This coverage contributes to the task verdict. Word precision and recall below use words and are separate full-text diagnostics, not substitutes for region coverage. The two measures use different units; neither establishes semantic correctness.", - "人为删改对照:只留下标题": "Edited control: keep only the title", - "把这份成功结果删到只剩": "Trim the successful output to just", - "再离线评分:仅 17 个字符,必需文字只命中 1/3 组,正文片段覆盖 7.84%,判定失败。这是检查规则的对照实验,不是某款浏览器的实际失败输出。": "and score it offline: only 17 characters remain, with 1/3 required text groups matched and 7.84% body-shingle coverage. It fails. This is a control that tests the rules, not an actual failed browser output.", - "这个例子也说明为什么不能把 Precision 单独叫作准确率。只剩标题时,它仍为 100%,因为留下的词都能在参考里找到;但 Recall 只有 10.53%,绝大部分正文已经丢失。任务验收会拒绝这个结果。": "This also shows why precision alone cannot be called accuracy. With only the title left, precision is still 100% because every remaining word appears in the reference. But recall is only 10.53%: most of the text is gone. Task acceptance rejects this output.", - "样本 2 · PDF 正文": "Example 2 · PDF body text", - "Attention 论文:不能只取回封面和摘要": "The Attention paper: a cover and abstract are not enough", - "这道题要求论文全部正文,包括后续页面、表格和附录。标准答案来自独立取得的源 PDF 正文;验收同时设置必需文字、最低长度和分布在全文中的覆盖要求。全文是任务目标;下面的通过门槛是允许一定遗漏的覆盖下限,不保证每一句、每个表格单元都完整,也不评价公式和版面还原。本次 Moli 样本的五区实际覆盖均为 100%,高于这个最低门槛。": "The task requests the full paper body, including later pages, tables, and appendices. The reference comes from an independently retrieved source PDF. Acceptance combines required text, minimum length, and coverage across the body. Full text is the goal, but the passing threshold is a coverage floor that allows some omissions. It does not guarantee every sentence or table cell, nor assess formulas or layout reconstruction. Moli's observed coverage is 100% in all five regions, above that floor.", - "同一 PDF,两条实际处理路线": "The same PDF, two actual processing paths", - "原始 PDF 字节": "Raw PDF bytes", - "本地提取文字": "Local text extraction", - "Chromium 本轮配置": "Chromium's tested configuration", - "未形成论文正文,未通过": "No paper body produced; failed", - "解析工作由本地 pypdf 完成。比较的是配置好的处理路线;这组结果没有测试给 Chromium 增加下载器和解析器后的表现。": "Local pypdf performs the parsing. This compares configured processing paths; these results do not test Chromium with an added downloader and parser.", - "PDF 本题的验收规则及 Moli 实际结果。": "Acceptance rules for this PDF task and Moli's observed results.", - "三个必需内容": "Three required text items", - "Attention Is All You Need、Transformer、Scaled Dot-Product Attention 全部命中": "Match Attention Is All You Need, Transformer, and Scaled Dot-Product Attention", - "3 / 3 命中": "3 / 3 matched", - "正文长度": "Body length", - "全文分区": "Full-text regions", - "五个连续参考分区,至少四区覆盖 ≥80%,且每一区都 ≥60%": "Five consecutive reference regions: at least four with coverage ≥80%, and every region ≥60%", - "五区均为 100%": "All five regions at 100%", - "排除 Just a moment...、Prove your humanity、Access Denied": "Exclude Just a moment..., Prove your humanity, and Access Denied", - "实际参考分区覆盖 · 按正文顺序,不是五个章节": "Observed reference-region coverage · in body order, not five chapters", - "分区按规范化参考文字的顺序近似等分。一份返回片段只能分配给一个参考分区,不能拿同一小段反复抵扣后文缺失。": "The normalized reference text is split into roughly equal consecutive regions. An output shingle can be assigned to only one reference region; the same short passage cannot repeatedly compensate for missing later text.", - "人为删改对照:只保留前 1,000 个字符": "Edited control: keep only the first 1,000 characters", - "离线评分拒绝了这个截断结果:长度低于 19,762,五个分区没有一个达到目标。它的 Precision 仍有 99.32%,但 Recall 仅 2.25%。只检查“标题出现过”或“返回的文字大多正确”,都不足以验收整篇论文。": "Offline scoring rejects the truncation: its length is below 19,762 and none of the five regions reaches the target. Precision remains 99.32%, but recall is just 2.25%. Checking that the title appears or that most returned words match is not enough to accept a full paper.", - "Moli+pypdf 在六道其他四款方案都未完成的 PDF 任务中取得了正文,构成文档组的六题差距。直接 DOCX 和两个嵌入式查看器仍未通过;普通 PDF 的这条处理路线尚未覆盖这些输入。": "Moli + pypdf retrieves the text on six PDF tasks that all four other methods fail, accounting for the document group's six-task gap. The direct DOCX task and two embedded viewers still fail; this ordinary-PDF path does not yet cover those inputs.", - "样本 3 · 列表记录": "Example 3 · List records", - "装饰蜡烛列表:检查记录,不固定每天的商品名单": "Decorative-candle list: check records without freezing the daily catalog", - "Anthropologie 装饰蜡烛列表": "Anthropologie decorative-candle list", - "商品、价格和顺序会变。这道题的标准答案是一组题目声明的结构要求:至少三条具名且详情链接不同的本站记录,并保留 Decorative Candles、Price、Color、Brand 四组稳定文字。独立页面快照用于确认这些字段与页面内容存在,不把某天的商品名单固定成答案。": "Products, prices, and ordering change. This task's reference is a declared structural contract: at least three named records with distinct detail links on the site, plus the stable text Decorative Candles, Price, Color, and Brand. An independent page snapshot confirms that the fields and content exist; it does not freeze one day's catalog as the answer.", - "实际返回中的一条记录 · 提取名称与详情链接": "One record from the actual output · name and detail link extracted", - "名称": "Name", - "详情路径": "Detail path", - "列表通过要求所有结构与稳定文字检查同时满足。": "The list must satisfy every structural and stable-text check to pass.", - "名称与详情入口": "Name and detail link", - "具名链接;域名是 www.anthropologie.com 或 www.anthropologie.com.cn,路径符合 /shop/[^/]+": "Named link; host is www.anthropologie.com or www.anthropologie.com.cn, with a path matching /shop/[^/]+", - "Moli 形成 72 条记录": "Moli produces 72 records", - "数量与去重": "Count and deduplication", - "按详情链接去重后,至少 3 条完整记录": "At least 3 complete records after deduplication by detail link", - "Moli 为 72;Chromium 为 0": "Moli: 72; Chromium: 0", - "稳定页面文字": "Stable page text", - "Decorative Candles、Price、Color、Brand 四组全部命中": "Match all four groups: Decorative Candles, Price, Color, Brand", - "Moli 命中 4/4": "Moli matches 4/4", - "页面是否可用于任务": "Page usable for the task", - "错误页、登录阻断页不能替代目标列表": "Error pages and login barriers cannot replace the target list", - "Moli 本次通过;其他四款未形成合格结果": "Moli passes this call; the other four produce no acceptable result", - "人为删改对照:把同一商品重复十遍": "Edited control: repeat the same product ten times", - "保留四组稳定文字,复制一条真实商品记录十次。评分器去重后只认一条,低于三条要求,判为失败。输出变长没有增加有效记录。": "Keep the four stable-text groups and copy one real product record ten times. After deduplication, the scorer counts only one record, below the required three, and fails it. More output has not added valid records.", - "还有一条需要明确的边界:把三条真实详情链接的名称换成虚构名称,这套结构检查仍会通过。它能确认“存在三条具名详情入口”,没有核验每个名称与商品是否事实相符。因此本文把它称为结构验收,不把这项通过率说成商品信息准确率。": "There is another limit: replacing the names on three real detail links with invented names still passes these structural checks. They establish that three named detail links exist, but do not verify that each name correctly describes its product. This is structural acceptance, not a measure of product-information accuracy.", - "Moli 这次主调用用时 6.07 秒,但后续三次诊断只有一次成功、两次为空。一次拿到 72 条记录证明这条路线在当时完成了任务,不能代替预先安排的重复运行,也不能据此估计长期成功率。": "Moli's primary call takes 6.07 seconds, but three later diagnostic calls yield one success and two empty responses. Retrieving 72 records once establishes that the path completed the task at that time. It does not replace planned repeated runs or estimate a long-term success rate.", - "样本 4 · 资源地址": "Example 4 · Resource URL", - "图片详情页:找到指定原图,导航链接不算": "Image detail page: find the required original image, not navigation links", - "Pexels 森林摄影作品 12377231 的详情页": "Pexels detail page for forest photograph 12377231", - "这道题只要目标图片地址,不要求下载图片,也不测试图片理解。标准答案由独立源页与浏览器核对,登记的是作品 12377231 对应的图片 URL,Logo 和推荐作品都不能替代它。": "This task requires only the target image URL, not a download or image understanding. The reference was checked against an independent source page and browser observation. It registers the image URL for photograph 12377231; a logo or recommended image cannot replace it.", - "预登记目标 · 作品编号是 12377231": "Registered target · photograph ID 12377231", - "Moli 实际交回的地址带有 cs、dl、fm 等查询参数。评分器从返回文字、链接或明确的资源元数据里收集候选地址,再与目标匹配。相对链接先转成完整地址;对 Pexels 这类已声明的图片服务,尺寸等查询参数不影响同一图片的身份。输入地址本身和浏览器最后停留的地址不算“发现了资源”。": "Moli's actual URL includes query parameters such as cs, dl, and fm. The scorer collects candidate URLs from returned text, links, or explicit resource metadata, then matches them to the target. Relative links are resolved first. For declared image services such as Pexels, size and similar query parameters do not change an image's identity. The input URL and final browser location do not count as resource discovery.", - "Moli:目标命中 1/1,通过": "Moli: target matched 1/1, passed", - "返回内容里包含目标原图地址。即使结果还带有导航和其他图片,命中本题唯一必需资源即可完成地址定位。": "The output contains the required original-image URL. Navigation and other images may remain, but matching this task's sole required resource completes URL discovery.", - "Chromium:目标命中 0/1,未通过": "Chromium: target matched 0/1, failed", - "这次访问遇到 HTTP 403,返回正文为 0 个字符,没有交付目标 URL;本题不能通过。": "This request encounters HTTP 403 and returns 0 body characters, without the target URL; the task fails.", - "资源命中率是“找到的必需资源数 ÷ 登记的必需资源数”,本题只有一个目标,因此要达到 1/1。这一类不会拿正文长度或全文 F1 代替 URL 匹配;找对地址也不等于已经成功下载了文件。": "Resource coverage is required resources found divided by required resources registered. This task has one target, so it needs 1/1. Body length and full-text F1 do not replace URL matching, and finding the correct URL does not establish a successful file download.", - "同类离线对照:保留文字,换掉目标图片 URL": "Offline control on a similar task: keep the text, change the image URL", - "在另一道真实图片地址题 image-03 中,仅替换目标资源地址,保留其余 1,662 个字符的返回文字。目标命中由 1/1 变成 0/1,任务随之失败。这是人为对照,不计入浏览器成绩。": "On another real image-URL task, image-03, replace only the target resource URL while retaining the other 1,662 output characters. Target coverage drops from 1/1 to 0/1 and the task fails. This edited control does not count toward browser scores.", - "Moli 在这道图片题胜出,但在 Blender 官方视频下载包题中没有找到指定的 1080p、30fps、normal 版本入口,Chromium、Lightpanda 和 Obscura 找到了。资源发现仍要按具体目标逐项测试。": "Moli wins this image task, but misses the specified 1080p, 30fps, normal-version download in the official Blender video-package task. Chromium, Lightpanda, and Obscura find it. Resource discovery still needs target-by-target testing.", - "样本 5 · 原始文件": "Example 5 · Original file", - "直接 PNG:名字和大小都对,也可能不是同一个文件": "Direct PNG: the right name and size do not prove it is the same file", - "这道题要交回原始 PNG 字节。独立获取的标准文件长 8,090 字节,并登记了 SHA-256 文件指纹;验收把实际交付文件与它核对。这里的标准答案是文件,重复输入 URL 或返回图片说明文字都不能通过。": "This task requires raw PNG bytes. The independently retrieved reference file is 8,090 bytes long, with a registered SHA-256 fingerprint. Acceptance compares the delivered file with that reference. Repeating the input URL or returning an image description cannot pass this file task.", - "独立标准文件与 Moli 实际文件的核对项": "Checks comparing the independent reference file with Moli's actual file", - "PNG 格式签名": "PNG format signature", - "最低长度": "Minimum length", - "达标": "Meets requirement", - "文件判定要求长度至少 32 字节、能识别格式签名,并与题目登记的指纹相同;若一题还指定 MIME 类型,也须匹配。此题未另设 MIME 限制。返回多个文件时,只要有一个文件通过上述全部检查,就满足文件验收。8,090 是本次标准文件的实际大小,不是单独设置的长度门槛。": "File acceptance requires at least 32 bytes, a recognized format signature, and a match to the registered fingerprint. A declared MIME type must also match; this task has no additional MIME restriction. If multiple files are returned, one file passing all these checks satisfies acceptance. The 8,090 bytes are this reference file's actual size, not a separate length threshold.", - "Moli 在 1.43 秒内交回了文件,本题通过。Chromium 和 Lightpanda 的本轮路线没有交付原始字节,Obscura 与 Kitesurf 返回了不可用文本,均未完成文件题。相同规则也用于两个直接视频任务,Moli 分别交回 330,618 字节的 WebM 和 1,128,375 字节的 MP4。": "Moli returns the file in 1.43 seconds and passes. The tested Chromium and Lightpanda paths deliver no raw bytes; Obscura and Kitesurf return unusable text. All fail the file task. The same rules apply to two direct-video tasks, where Moli returns a 330,618-byte WebM and a 1,128,375-byte MP4.", - "人为删改对照:只改最后一个字节": "Edited control: change only the last byte", - "PNG 的开头签名没变,长度仍是 8,090 字节,但文件指纹改变,重新校验后判为失败。格式和大小相同,不足以证明交回了预期文件。": "The PNG's opening signature is unchanged and its length remains 8,090 bytes, but the fingerprint changes and revalidation fails. Matching format and size are insufficient to prove that the expected file was returned.", - "文件指纹只用于确认与已独立核对的标准文件一致,不能证明这张图片表达了什么,也不测试图片能否被视觉模型正确理解。它适用于本题这种内容固定的文件;不能把每天变化的文件仍与旧指纹硬比。": "The fingerprint checks agreement with an independently verified reference file. It says nothing about the image's meaning or a vision model's understanding. This works for fixed-content files such as this one; a daily-changing file should not be forced to match an old fingerprint.", - "阈值与对照,支持什么程度的判断": "What the thresholds and controls establish", - "80% 的分区目标、60% 的硬下限来自统一正文覆盖规则;76 和 19,762 则是这两道题当前登记的最低长度。其中 PDF 的最低长度恰为 39,524 字符参考的一半。它们是工程验收下限,本轮没有通过阈值敏感性实验,证明这些数值是唯一或最优的选择。": "The 80% region target and 60% hard floor come from the shared body-coverage rules. The values 76 and 19,762 are the two tasks' currently registered minimum lengths. The PDF minimum is half its 39,524-character reference. These are engineering acceptance floors; this run has no threshold-sensitivity experiment establishing them as unique or optimal choices.", - "评分规则也经历过修订:独立复核曾发现排版差异导致的误判,以及正文不完整却被放行的情况,随后对各方案统一规范化、补充正文覆盖检查并离线重算。因此当前成绩对应最终复核口径,不能把所有规则都描述成第一次调用前已经冻结。": "Scoring rules have also been revised. Independent review found false verdicts caused by formatting differences and incomplete text that had passed. Shared normalization and body-coverage checks were then applied across methods, followed by offline rescoring. Current scores use the final reviewed rules; not every rule was frozen before the first call.", - "五个样例分别拒绝了正文遗漏、文档截断、重复记录、错误资源地址和文件字节变化。它们说明规则能识别这些具体失败方式,而不是证明任意网站都能被正确评分。列表中的虚构名称能通过结构检查,也暴露了当前指标没有覆盖的事实核验。": "The five examples reject missing body text, a truncated document, duplicate records, an incorrect resource URL, and changed file bytes. They show that the rules detect these specific failures, not that they score every website correctly. Invented product names passing the structural check also expose factual verification that the current metrics do not cover.", - "任务通过率按完成题数计算,Moli 的结果是 135 ÷ 150 = 90.0%。只有存在完整参考正文的任务,才另算 Precision、Recall 和 F1:分别衡量返回文字与参考的重合比例、参考正文找回比例,以及两者的综合结果。它们不检查事实真伪或时效;没有完整参考的任务不参与全文平均。": "Task pass rate counts completed tasks; Moli's result is 135 ÷ 150 = 90.0%. Tasks with complete body references also receive precision, recall, and F1: output overlap with the reference, reference text recovered, and their combined score. These do not check factual correctness or freshness. Tasks without complete references are excluded from full-text averages.", - "03 / 五款受测方案": "03 / Five tested methods", - "浏览器名称之外,还要看怎样取得结果": "Beyond the browser name, examine how it produces the result", - "同一款浏览器可以接不同的提取器和文件处理工具。本轮比较的是下面五条配置好的路线,既包含本地运行,也包含远端服务。产品定位参考各自官方说明,分数来自相同的 150 个任务。": "A browser can be paired with different extractors and file-processing tools. This run compares the five configured paths below, including local execution and a remote service. Product descriptions follow official documentation; scores use the same 150 tasks.", - "运行位置和输出路径都是比较条件;这不是单独测量五个浏览器内核。": "Execution location and output path are part of the comparison; the five browser engines are not measured in isolation.", - "方案与定位": "Method and purpose", - "本轮运行方式": "Execution in this run", - "交付路径": "Output path", - "通过数": "Passed", - "面向 AI Agent 的 Rust 浏览器": "Rust browser for AI agents", - "本地": "Local", - "原生 Markdown;直接文档与媒体取原始响应,PDF 用 pypdf 解析": "Native Markdown; raw responses for direct documents and media, with pypdf parsing PDFs", - "通用开源浏览器项目": "General-purpose open-source browser project", - "本地无头模式": "Local, headless mode", - "取得页面 DOM,再用 node-html-markdown 2.0.0 转换": "Retrieve page DOM, then convert with node-html-markdown 2.0.0", - "用 Zig 从头开发的自动化浏览器": "Automation browser built from scratch in Zig", - "采用 V8 的 Rust 无头浏览器": "Rust headless browser using V8", - "本地标准模式": "Local, standard mode", - "原生 Markdown;未启用 stealth 或备用引擎": "Native Markdown; no stealth mode or alternative engine enabled", - "Cloudflare 面向 Agent 的浏览器方案": "Cloudflare browser offering for agents", - "远端 Browser Run": "Remote Browser Run", - "发送 URL,通过服务接口返回 Markdown": "Send a URL; receive Markdown through the service endpoint", - "Moli 与 Chromium:文档覆盖和网页正文,各有值得细看的结果": "Moli and Chromium: a closer look at documents and page text", - "无头模式只是运行时不显示窗口;DOM 是浏览器维护的页面结构。Chromium 这条路线把页面结构转成文本,普通网页完成 99/100,完整参考下的正文找回比例与 F1 也高于 Moli。Moli 的普通网页为 97/100,但它的原始响应路径能把 PDF 和媒体送到后续解析或文件校验环节,增加了本轮混合输入的完成数。": "Headless mode runs without displaying a window; the DOM is the page structure maintained by the browser. Chromium's path converts that structure to text, completing 99/100 general pages with higher recall and F1 than Moli on complete references. Moli scores 97/100 on general pages, but its raw-response path delivers PDFs and media to parsing or file-validation steps, increasing its completed tasks in this mixed-input run.", - "这种差异直接影响接入方式。需要网页正文时,应检查页面取到了多少;需要 PDF 或原文件时,应检查整条路线是否交付了可解析字节。给 Chromium 另接下载器和 PDF 解析器,或改变其他浏览器的等待与提取策略,都形成新的受测配置,需要重新比较。": "That affects integration. For page text, check how much content was retrieved. For PDFs and original files, check whether the complete path delivers parseable bytes. Adding a downloader and PDF parser to Chromium, or changing another browser's waiting and extraction strategy, creates a new configuration that needs another comparison.", - "Lightpanda:长标准与 IMDb 是它的具体成功例子": "Lightpanda: concrete successes on long standards and IMDb", - "Lightpanda 不是 Chromium 分支。本轮普通网页完成 92/100,三篇 Moli 未完成的 W3C 标准都取到了正文,IMDb 任务也只有它通过。共同成功速度子集中,P50 为 3.49 秒,P95 在本文五款方案中最低;文档 1/10、热门站点 4/10 则显示这条路线在混合输入中的缺口。": "Lightpanda is not a Chromium fork. It completes 92/100 general pages, retrieves all three W3C standards that Moli missed, and is the only method here to pass IMDb. In the shared-success latency subset, its P50 is 3.49 seconds and its P95 is lowest among the five methods. Scores of 1/10 for documents and 4/10 for popular sites show gaps in this mixed-input path.", - "Obscura:取得部分资源入口,但当前配置等待较长": "Obscura: some resource-discovery successes, with longer waits in this configuration", - "Obscura 的标准模式完成 90/100 个普通网页和 5/10 个热门站点任务,也找到了 Moli 未完成的 Blender 下载包入口。相同成功任务上的 P50 为 11.69 秒,是本文五款方案中最高的。这个结果对应当前模式与端到端调用,不能外推到未启用的其他模式。": "Obscura's standard mode completes 90/100 general pages and 5/10 popular-site tasks, and finds the Blender package link Moli missed. Its P50 on the same successful tasks is 11.69 seconds, the highest of the five methods here. This describes the current mode and end-to-end call, not other modes that were not enabled.", - "Kitesurf:由云端运行,延迟包含网络往返": "Kitesurf: cloud execution, with network round trips included in latency", - "Kitesurf 完成 88/100 个普通网页和 8/10 个动态任务,YouTube 题是其他四款未完成的成功例子。其共同成功子集 P50 为 5.87 秒,包含客户端到服务的网络往返与远端处理。云端运行省去了调用方管理本地浏览器的步骤,但这轮没有测量托管成本、并发或扩容能力。": "Kitesurf completes 88/100 general pages and 8/10 dynamic tasks; YouTube is a success the other four miss. Its shared-success P50 is 5.87 seconds, including the client-service round trip and remote processing. Cloud execution removes the need to manage a local browser, but this run does not measure hosting cost, concurrency, or scaling.", - "04 / 尚未解决的问题": "04 / Remaining gaps", - "05 / 结论与下一轮": "05 / Conclusions and next round", - "在相同成功任务上,Moli 的中位耗时在本文五款方案中最低": "On the same successful tasks, Moli has the lowest median latency among the five methods here", - "它的突出结果是文档 7/10、图片 10/10、视频 7/10;普通网页完成 97/100,与 Chromium 的 99/100 接近。在共同成功的速度子集中,它的中位耗时在本文五款方案中最低。三个 W3C 页面停在安全验证页,直接 DOCX 和两个嵌入式查看器也未完成。": "Its strongest results are documents at 7/10, images at 10/10, and videos at 7/10. General pages score 97/100, close to Chromium's 99/100. In the shared-success latency subset, its median is lowest among the five methods here. Three W3C pages stop at security verification; the direct DOCX task and two embedded viewers also fail.", - "它完成了 92/100 个普通网页和 8/10 个动态任务;三篇 Moli 未完成的 W3C 标准都取得了正文,IMDb 任务也只有它通过。在独立速度子集中,它的 P50 为 3.49 秒,样本 P95 在本文五款方案中最低。文档 1/10、热门站点 4/10,则说明它在本次混合输入中的覆盖仍有缺口。": "It completes 92/100 general pages and 8/10 dynamic tasks. It retrieves the body text of all three W3C standards Moli missed and is the only method here to pass the IMDb task. In the separate latency subset, its P50 is 3.49 seconds and its sample P95 is lowest among the five methods here. Scores of 1/10 for documents and 4/10 for popular sites show gaps in this mixed-input sample.", - "Moli 的 Web Fetch 表现:五款浏览器、150 个任务": "Moli's Web Fetch performance: five browsers, 150 tasks", - "20 题中 · 七条路线共同成功的 8 题": "From 20 tasks · 8 passed by all seven paths", - "把内容类型分开看,Moli 与 Chromium 在 120 个网页类任务中都完成了 111 个;文档、图片和视频合计 30 题,分别完成 24 个和 14 个。因此,Moli 相对 Chromium 的 10 题净差,全部来自文档与媒体。这里比较的是完整处理路线:Moli 的 PDF 成绩包含获取文件后由本地 pypdf 提取文字的步骤,其他方案采用本轮配置的页面转文本路径。": "Across 120 web tasks, Moli and Chromium each completed 111. Across 30 document, image, and video tasks, they completed 24 and 14 respectively. Moli's net 10-task lead over Chromium therefore comes entirely from documents and media. This compares complete processing paths: Moli's PDF scores include local pypdf text extraction after file retrieval; the other methods use the page-to-text paths configured for this run.", - "Web Fetch 不只需要处理 HTML。一篇 PDF 要交回正文,一张图片详情页可能只要求找到原图链接,直接图片地址则要交回文件。相同的“抓取成功”,放到这些任务里有不同含义。以下五种交付要求各用一个例子说明;箭头表示处理过程,具体浏览器可以采用不同实现。": "Web Fetch handles more than HTML. A PDF task needs text; an image detail page may need only the original-image link; a direct image URL needs the file itself. Successful retrieval means something different in each case. Each of the five output requirements below has an example; arrows show the processing sequence, while individual browsers may implement it differently.", - "交付要求与统计分组是两回事:图片和视频组都可能要求资源地址或原文件,热门站点也可能验收正文或列表。统计时保留六个分组:100 个普通网页,以及动态网页、热门站点、文档、图片、视频各 10 个。普通网页包含文档站、百科、项目说明和标准文本;动态网页专门检查延迟出现、后续取数和 iframe 内的内容;热门站点考察真实站点上较复杂的页面与列表。": "Output requirements differ from reporting groups: image and video tasks may require resource URLs or original files; popular-site tasks may check body text or lists. Results retain six groups: 100 general pages, plus 10 each for dynamic pages, popular sites, documents, images, and videos. General pages include documentation sites, encyclopedias, project descriptions, and standards. Dynamic tasks examine delayed content, subsequent data retrieval, and iframe content; popular-site tasks cover more complex real-world pages and lists.", - "它的突出结果是文档 7/10、图片 10/10、视频 7/10;普通网页完成 97/100,与 Chromium 的 99/100 接近。在共同成功的速度子集中,它的中位耗时最低。三个 W3C 页面停在安全验证页,直接 DOCX 和两个嵌入式查看器也未完成。": "Its strongest results are documents at 7/10, images at 10/10, and videos at 7/10. General pages score 97/100, close to Chromium's 99/100. It has the lowest median latency in the shared-success subset. Three W3C pages stop at security verification; the direct DOCX task and two embedded viewers also fail.", - "六个只有 Moli+pypdf 路线完成的 PDF 任务支持了这组结果,构成文档类别的六题差距。结果支持将 Moli 原始文件获取与解析器组合用于这组 PDF 输入;直接 DOCX 和两个嵌入式查看器未通过,仍需单独完善。": "Six PDF tasks completed only by the Moli + pypdf path account for the six-task document gap. These results support combining Moli's raw-file retrieval with a parser for this set of PDFs. The direct DOCX task and two embedded viewers failed and still need separate work.", - "Web Fetch 实测": "Web Fetch benchmark", - "150 个任务 · 五款浏览器": "150 tasks · five browsers", - "Moli 的 Web Fetch 表现": "Moli's Web Fetch performance", - "当 Agent 需要读取网页、论文和媒体文件,浏览器能否把所需内容完整交回来?我们用同一组任务,对比了 Moli、Chromium、Lightpanda、Obscura 和 Kitesurf。": "When an agent needs to read web pages, papers, and media files, can its browser return all the required content? We compared Moli, Chromium, Lightpanda, Obscura, and Kitesurf on the same tasks.", - "Moli 任务通过率": "Moli task pass rate", - "网页类完成数量": "Web tasks completed", - "Moli 与 Chromium · 各 120 题": "Moli and Chromium · 120 tasks each", - "文档与媒体完成数量": "Document and media tasks completed", - "Moli 与 Chromium · 各 30 题": "Moli and Chromium · 30 tasks each", - "Moli 中位耗时": "Moli median latency", - "独立速度子集 · 8 个共同成功任务": "Separate latency subset · 8 shared successes", - "Agent 怎样用浏览器": "How agents use browsers", - "主要结果": "Main results", - "任务与验收": "Tasks and acceptance", - "五款浏览器": "Five browsers", - "实际输出": "Actual outputs", - "改进与下一轮": "Gaps and next steps", - "背景": "Background", - "读网页和操作网页,需要两种不同的测试": "Reading pages and interacting with them need different tests", - "“读完这篇论文,整理主要结论”和“打开网站,搜索产品,再把它加入购物车”,都会用到浏览器,但对 Agent 来说是两种工作。前者已经知道要读取的地址,需要取得内容;后者需要根据页面变化决定下一步,反复执行搜索、点击、填写和检查。": "Both 'read this paper and summarize its findings' and 'open the site, search for a product, and add it to the cart' use a browser. But they ask different things of an agent. The first starts with a known address and needs its content. The second requires repeated searches, clicks, form inputs, and checks, with each action chosen from the page's current state.", - "Web Fetch:给定 URL,取回内容": "Web Fetch: retrieve content from a given URL", - "例如把论文链接变成可读正文,再交给模型总结。工具负责获取网页或文件,Agent 使用返回结果继续回答问题。": "A paper URL becomes readable text that a model can summarize. The tool retrieves the page or file; the agent uses the returned content to continue answering the question.", - "获取与整理": "Retrieve and extract", - "正文、记录或文件": "Text, records, or files", - "Browser Use:观察页面,连续执行操作": "Browser Use: observe a page and carry out a sequence of actions", - "例如搜索产品、调整筛选条件、翻页和填写表单。Agent 需要多次观察页面,并根据上一步结果选择下一步动作。": "Examples include searching for products, adjusting filters, changing pages, and filling forms. The agent observes the page repeatedly and chooses each action based on the previous result.", - "观察": "Observe", - "选择动作": "Choose an action", - "执行": "Act", - "再观察": "Observe again", - "这份评测只看 Web Fetch。它关心从 URL 到可用内容这一段:该有的正文是否齐全,列表是否包含要求的字段,返回的文件是否正确,以及这一过程花了多久。模型拿到内容后能否推理正确、浏览器能否完成连续网页操作,都需要另外评测。": "This benchmark covers Web Fetch: whether a URL yields complete required text, records with the requested fields, or the correct file, and how long that takes. Reasoning correctly from that content and completing sequences of web interactions require separate evaluations.", - "浏览器的选择也因此有了具体标准。即使都能打开一个页面,不同方案交回的东西仍可能不同:完整正文、只有标题的页面框架、安全验证提示,或者下游无法解析的文件内容。我们把这些差异放进同一组任务中比较。": "That gives browser selection concrete criteria. Browsers that can all open a page may return very different things: complete text, a title-only shell, a security prompt, or file content the next tool cannot parse. We compare those differences through a shared task set.", - "01 / 主要结果": "01 / Main results", - "Moli 完成 135 个任务,优势集中在文档与媒体": "Moli completed 135 tasks, with its lead concentrated in documents and media", - "五款浏览器的任务通过数": "Tasks passed by the five browsers", - "把内容类型分开看,Moli 与 Chromium 在 120 个网页类任务中都完成了 111 个;文档、图片和视频合计 30 题,分别完成 24 个和 14 个。因此,Moli 相对 Chromium 的 10 题净差,全部来自文档与媒体。": "By content type, Moli and Chromium each completed 111 of the 120 web tasks. Across 30 document, image, and video tasks, they completed 24 and 14 respectively. Moli's net 10-task lead over Chromium therefore comes entirely from documents and media.", - "网页类持平,文档与媒体相差 10 题": "Web results tie; documents and media differ by 10 tasks", - "网页类包含普通网页、动态网页和热门站点;文档、图片、视频各有 10 题。": "Web tasks cover general pages, dynamic pages, and popular sites. Documents, images, and videos each have 10 tasks.", - "总数持平也会掩盖单个网页的差异。普通网页中,Moli 为 97/100,Chromium 为 99/100;热门站点为 6/10 对 4/10;动态网页同为 8/10。Moli 在热门站点多完成的两题,抵消了它在普通网页上的两题差距。": "Equal totals can hide differences on individual pages. On general pages, Moli scored 97/100 and Chromium 99/100; popular-site scores were 6/10 versus 4/10; dynamic-page scores were both 8/10. Moli's two additional popular-site passes offset its two fewer general-page passes.", - "在相同成功任务上,Moli 的中位耗时最低": "Moli had the lowest median latency on the same successful tasks", - "速度另设一组预先选定的 20 个普通网页。包括三个 Moli 版本在内的七条受测路线共同完成了其中 8 个,速度图只比较这 8 个相同任务,避免把各自成功的不同网页混在一起。": "Latency uses a separate, preselected set of 20 general pages. All seven tested paths, including three Moli versions, completed 8 of them. The chart compares only those same 8 tasks, rather than mixing different successful subsets.", - "共同成功任务的中位耗时 · 越短越快": "Median latency on shared successes · shorter is faster", - "02 / 任务与验收": "02 / Tasks and acceptance", - "从一个地址,到一个可以检查的结果": "From an address to a result we can check", - "Web Fetch 不只需要处理 HTML。一篇 PDF 要交回正文,一张图片详情页可能只要求找到原图链接,直接图片地址则要交回文件。相同的“抓取成功”,放到这些任务里有不同含义。下面五个例子展示了常见的处理过程;箭头表示典型路径,具体浏览器可以采用不同实现。": "Web Fetch handles more than HTML. A PDF task needs text; an image detail page may need only the original-image link; a direct image URL needs the file itself. Successful retrieval means something different in each case. The five examples below show typical paths; individual browsers may implement them differently.", - "网页正文": "Page body text", - "网页 URL": "Page URL", - "取得所需页面内容": "Retrieve required page content", - "输入 Example Domain 页面,结果应有标题、用途说明和更多信息链接。只返回标题,或返回安全验证提示,都无法完成这道题。": "For the Example Domain page, the output must include its title, purpose, and further-information link. A title alone or a security-verification prompt does not complete the task.", - "PDF 文档": "PDF document", - "获取原始文件": "Retrieve original file", - "解析文字": "Parse text", - "输入《Attention Is All You Need》的 PDF 地址,检查结果是否覆盖参考正文。当前 Moli 路线由本地 pypdf 完成解析,文件获取和解析共享时限。": "Given the PDF URL for Attention Is All You Need, check whether the output covers the reference body. The current Moli path uses local pypdf parsing; retrieval and parsing share the time limit.", - "列表记录": "List records", - "列表 URL": "List URL", - "取得条目与字段": "Retrieve entries and fields", - "输出不重复记录": "Return unique records", - "装饰蜡烛列表至少需要 3 条商品名称和详情链接,还要保留指定的稳定页面文字。空记录、重复记录和缺字段不能用来凑数。": "The decorative-candle list needs at least 3 product names with detail links, plus specified stable page text. Empty, duplicate, or incomplete records do not count.", - "图片或视频地址": "Image or video URL", - "资源详情页": "Resource detail page", - "定位目标资源": "Locate required resource", - "输出图片或视频 URL": "Return image or video URL", - "从下载页找到指定资源,交付的是目标地址。页面上有很多链接,拿到导航链接或无关素材链接都不算完成。": "Find the specified resource on a download page and return its URL. Navigation links or unrelated assets do not satisfy the task.", - "原始文件": "Original file", - "直接文件 URL": "Direct file URL", - "获取字节": "Retrieve bytes", - "校验并交付文件": "Validate and deliver file", - "输入 PNG、WebM 或 MP4 的直接地址,检查格式、长度和文件指纹。图片说明文字不能代替 PNG 字节,文件题也不测试模型能否看懂图片。": "Given a direct PNG, WebM, or MP4 URL, check the format, length, and file fingerprint. An image description cannot replace PNG bytes. These tasks do not test whether a model understands the image.", - "150 个任务怎样组成": "What makes up the 150 tasks", - "统计时保留六个分组:100 个普通网页,以及动态网页、热门站点、文档、图片、视频各 10 个。普通网页包含文档站、百科、项目说明和标准文本;动态网页专门检查延迟出现、后续取数和 iframe 内的内容;热门站点考察真实站点上较复杂的页面与列表。": "Results retain six groups: 100 general pages, plus 10 each for dynamic pages, popular sites, documents, images, and videos. General pages include documentation sites, encyclopedias, project descriptions, and standards. Dynamic tasks examine delayed content, subsequent data retrieval, and iframe content; popular-site tasks cover more complex real-world pages and lists.", - "普通网页占三分之二,所以总分很大程度上反映这组样本的构成。10 个文档任务与 100 个普通网页任务不能代表全网的真实比例,小分组相差一题就是 10 个百分点。本文同时保留逐类结果和实际案例,让读者能判断差距是否发生在自己的输入类型上。": "General pages make up two-thirds of the sample, so they strongly influence the total. The ratio of 10 document tasks to 100 general-page tasks does not represent the web as a whole; one task in a small group changes its score by 10 percentage points. Category results and concrete examples help readers judge whether the differences affect their own input types.", - "成功判定跟着任务走,不只看请求有没有报错": "Acceptance follows the task, not just the request status", - "一个普通网页任务怎样判分": "How a general-page task is scored", - "题目要求至少 76 个字符,并包含标题、用途说明、更多信息链接三组内容。可以接受题目声明的等价表述,同时排除验证页、访问拒绝等错误内容,并检查参考正文区域覆盖。": "The task requires at least 76 characters and three content groups: title, purpose, and further-information link. Declared equivalent wording is accepted. Verification pages, access-denied responses, and other errors are excluded, and reference body-region coverage is checked.", - "可通过的结果": "An acceptable result", - "保留 Example Domain、用于文档示例的说明,以及 Learn more 或对应链接,并满足该题正文要求。": "Retain Example Domain, the explanation of its use in documentation examples, and Learn more or the corresponding link, while satisfying the task's body requirements.", - "不能通过的结果": "An unacceptable result", - "只剩 Example Domain 标题;或整页只有 Access denied。请求即使正常返回,也没有交付题目要求的内容。": "Only the Example Domain title remains, or the entire page says Access denied. A normal response still fails to deliver the required content.", - "参考内容独立于被测浏览器准备。对于动态网页,保留初始 HTTP 内容和独立渲染后可见的目标内容,核对页面是否真的需要后续加载。对经常变化的榜单和商品页,则检查稳定文字、必要字段、数量和唯一性,不把某一刻的价格和排名永久固定成答案。": "References are prepared independently of the tested browsers. Dynamic-page references retain initial HTTP content and independently rendered visible target content, so subsequent loading requirements can be checked. Frequently changing rankings and product pages are assessed through stable text, required fields, count, and uniqueness, without freezing a moment's prices or ranks as permanent answers.", - "任务通过率与正文完整度,分别回答什么": "What task pass rate and body coverage each tell us", - "任务通过率按完成题数计算,例如 Moli 的 135 ÷ 150 = 90.0%。正文质量另外检查:Recall 看参考正文找回了多少,Precision 看返回文字有多少落在参考范围内,F1 综合两者。导航、页脚和格式差异会影响这些数值;它们衡量文字重合,不验证内容的事实真伪或时效。": "Task pass rate counts completed tasks: for Moli, 135 ÷ 150 = 90.0%. Text quality is checked separately. Recall measures recovered reference text; precision measures returned text within the reference scope; F1 combines both. Navigation, footers, and formatting affect these numbers. They measure text overlap, not factual correctness or freshness.", - "一个结果可以满足任务的必要要求,同时带有多余导航;也可能找回部分正文,却漏掉一段必需内容而未通过。因此任务成败与全文指标分别保留。缺少完整参考的任务不参与全文平均,也不会把未测值当作零或满分。": "An output can satisfy the task while containing extra navigation. It can also recover some body text but fail because a required passage is missing. Task verdicts and full-text metrics therefore remain separate. Tasks without complete references are excluded from full-text averages; unmeasured values are neither zero nor perfect scores.", - "03 / 五款浏览器": "03 / Five browsers", - "相同 URL,五种取回内容的方式": "The same URL, five ways to retrieve its content", - "这五款软件都能用于自动化获取网页,但接入方式和本轮配置并不相同。比较完整处理路线,才能知道交付的结果从哪里来。下面的产品定位参考官方项目说明,成绩均来自本轮实测。": "All five tools can retrieve web pages automatically, but their interfaces and tested configurations differ. Comparing the complete processing paths explains how each output is produced. Product descriptions below follow official documentation; scores come from this benchmark.", - "Moli 是面向 AI Agent 的 Rust 浏览器,提供网页获取与浏览器自动化接口。本轮普通网页使用原生 Markdown 输出;直接文档和媒体使用原始响应路径,PDF 再由本地解析器提取文字。": "Moli is a Rust browser for AI agents, with web retrieval and browser-automation interfaces. General-page tasks use native Markdown output. Direct documents and media use the raw-response path, with a local parser extracting PDF text.", - "本地 Moli": "Local Moli", - "原生 Markdown,或原始文件+文档解析": "Native Markdown, or raw file + document parsing", - "它的突出结果是文档 7/10、图片 10/10、视频 7/10;普通网页完成 97/100,与 Chromium 的 99/100 接近。在共同成功的速度子集中,它的中位耗时最低。三个 W3C 页面停在安全验证页,DOCX 和嵌入式查看器也仍有未完成任务,覆盖范围还不能等同于完整 Chromium。": "Its strongest results are documents at 7/10, images at 10/10, and videos at 7/10. General pages score 97/100, close to Chromium's 99/100. It has the lowest median latency in the shared-success subset. Three W3C pages stop at security verification, while DOCX and embedded viewers also have failures; this coverage is not equivalent to the full Chromium browser.", - "Moli 官方项目": "Official Moli project", - "Chromium 是开源浏览器项目,也是许多浏览器产品的基础。本轮以无头模式运行,即无需显示浏览器窗口;取得页面 DOM 后,用 node-html-markdown 2.0.0 转成 Markdown。DOM 可以理解为浏览器维护的页面结构。": "Chromium is an open-source browser project underlying many browser products. It runs headlessly here, without displaying a window. After retrieving the page DOM, node-html-markdown 2.0.0 converts it to Markdown. The DOM is the page structure maintained by the browser.", - "本地 Chromium": "Local Chromium", - "页面 DOM": "Page DOM", - "HTML 转 Markdown": "HTML to Markdown", - "它在普通网页完成 99/100,且完整正文的找回比例与 F1 都高于 Moli,是本文普通 HTML 正文的重要基线。文档只有 1/10,反映的是本轮 DOM 转文本路线面对文件的结果;增加文件下载与 PDF 解析器后,需要重新测试那条组合路线。": "It completes 99/100 general pages, with higher full-body recall and F1 than Moli, making it an important HTML-text baseline here. Its document score of 1/10 describes the tested DOM-to-text path on files. Adding file download and a PDF parser would require testing that combined path separately.", - "Chromium 官方项目": "Official Chromium project", - "Lightpanda 是用 Zig 从头开发的无头浏览器,面向 AI 与自动化,并非 Chromium 分支。本轮直接使用其原生 Markdown 输出。": "Lightpanda is a headless browser built from scratch in Zig for AI and automation, rather than a Chromium fork. This run uses its native Markdown output.", - "本地 Lightpanda": "Local Lightpanda", - "它完成了 92/100 个普通网页和 8/10 个动态任务;三篇 Moli 未完成的 W3C 标准都取得了正文,IMDb 任务也只有它通过。在独立速度子集中,它的 P50 为 3.49 秒,样本 P95 最低。文档 1/10、热门站点 4/10,则说明它在本次混合输入中的覆盖仍有缺口。": "It completes 92/100 general pages and 8/10 dynamic tasks. It retrieves the body text of all three W3C standards Moli missed and is the only method here to pass the IMDb task. In the separate latency subset, its P50 is 3.49 seconds and its sample P95 is lowest. Scores of 1/10 for documents and 4/10 for popular sites show gaps in this mixed-input sample.", - "Lightpanda 官方项目": "Official Lightpanda project", - "Obscura 是面向网页抓取和 AI Agent 的 Rust 无头浏览器,使用 V8 执行 JavaScript。本轮采用标准模式及原生 Markdown 输出,没有启用 stealth 模式或备用引擎。": "Obscura is a Rust headless browser for web scraping and AI agents, using V8 to execute JavaScript. This run uses standard mode and native Markdown output, without stealth mode or an alternative engine.", - "本地 Obscura 标准模式": "Local Obscura, standard mode", - "它完成了 90/100 个普通网页、5/10 个热门站点任务,也通过了 Moli 未完成的 Blender 资源定位题。共同成功子集的 P50 为 11.69 秒,是本文五款方案中最高的;这个数字说明当前配置的端到端等待较长,不能拿来评价未启用的其他模式。": "It completes 90/100 general pages and 5/10 popular-site tasks, including the Blender resource-discovery task Moli missed. Its shared-success P50 is 11.69 seconds, the highest of the five methods here. This indicates a longer end-to-end wait in the tested configuration; it does not evaluate modes that were not enabled.", - "Obscura 官方项目": "Official Obscura project", - "Kitesurf 是 Cloudflare 面向 Agent 的浏览器方案。本轮通过 Browser Run 的 Markdown 接口运行,指定 Kitesurf 作为浏览器。调用方把 URL 发给远端服务,再取得 Markdown;它与其他四种本地运行方式存在部署差异。": "Kitesurf is Cloudflare's browser offering for agents. This run selects Kitesurf through Browser Run's Markdown endpoint. The client sends a URL to the remote service and receives Markdown, a different deployment setup from the four locally run methods.", - "客户端": "Client", - "它完成了 88/100 个普通网页、8/10 个动态任务,并在 YouTube 题取得了其他四款未完成的结果。共同成功子集 P50 为 5.87 秒,包含公网往返及远端处理。使用这种服务可把浏览器运行交给云端,但本轮没有测量托管成本、扩容能力或生产并发。": "It completes 88/100 general pages and 8/10 dynamic tasks, and passes the YouTube task the other four missed. Its shared-success P50 is 5.87 seconds, including the public-network round trip and remote processing. This service moves browser operation to the cloud, but hosting cost, scaling, and production concurrency were not measured.", - "Kitesurf 官方说明": "Official Kitesurf documentation", - "04 / 实际输出": "04 / Actual outputs", - "看清差距,要把同一道题的输出放在一起": "Compare outputs from the same task to understand the difference", - "一篇 PDF:文件是否进入了解析步骤": "A PDF: did the file reach the parser?", - "取回 PDF 原始字节": "Retrieve raw PDF bytes", - "本地提取正文": "Extract body text locally", - "评测预先知道输入属于文档类型。图中流程没有测试未知 URL 的自动识别,也不能把本地解析器的工作归给 Moli 本身。": "The benchmark knows in advance that the input is a document. This path does not test automatic detection for unknown URLs; the local parser's work must not be attributed to Moli itself.", - "同一 PDF 任务的实际输出;比较的是本轮完整配置。": "Actual outputs for the same PDF task, comparing the complete tested configurations.", - "方案": "Method", - "返回结果": "Returned output", - "验收": "Acceptance", - "39,524 个字符,参考正文完整覆盖": "39,524 characters, with complete reference-body coverage", - "Chromium+DOM 转 Markdown": "Chromium + DOM to Markdown", - "0 个正文字符": "0 body characters", - "138 个字符,不足以覆盖论文正文": "138 characters, insufficient to cover the paper body", - "将部分 PDF 字节转成不可读文本": "Some PDF bytes converted into unreadable text", - "六个只有 Moli 完成的 PDF 任务重复验证了这条路线,构成文档类别的六题差距。结果支持将 Moli 原始文件获取与解析器组合用于这组 PDF 输入;直接 DOCX 和两个嵌入式查看器未通过,仍需单独完善。": "Six PDF tasks completed only by Moli repeatedly validate this path and account for the six-task document gap. The results support combining Moli's raw-file retrieval with a parser for these PDF inputs. Direct DOCX and two embedded viewers failed and still need separate work.", - "原文件:交回字节,后续工具才能继续处理": "Original files: downstream tools need the bytes", - "直接 PNG 任务需要取得一张 8,090 字节的图片。Moli 在 1.43 秒内返回原文件;Chromium 和 Lightpanda 当前路线没有交回文件字节,Obscura 和 Kitesurf 返回了不可用文本。两个直接视频任务也由 Moli 取得了正确文件。": "The direct PNG task requires an 8,090-byte image. Moli returns the original file in 1.43 seconds. The current Chromium and Lightpanda paths return no file bytes; Obscura and Kitesurf return unusable text. Moli also retrieves the correct files for both direct-video tasks.", - "Moli 交回的三个直接文件": "Three direct files returned by Moli", - "三个文件均通过格式、长度与 SHA-256 指纹检查。这些结果可供后续 OCR、视觉模型、转码或存档使用,评测没有执行这些下游任务。": "All three files pass format, length, and SHA-256 fingerprint checks. The outputs could feed OCR, vision models, transcoding, or archives; those downstream tasks were not run in this evaluation.", - "文件题成功,也不保证能在复杂页面中发现正确资源。Blender 下载资源题由 Chromium、Lightpanda 和 Obscura 完成,Moli 没有找到目标地址。两项能力需要分别测试。": "Passing a file task does not guarantee finding the right resource on a complex page. Chromium, Lightpanda, and Obscura complete the Blender download-resource task, where Moli misses the target URL. These abilities need separate tests.", - "商品列表:72 条记录可用,但一次成功还不够": "Product list: 72 usable records, but one success is not enough", - "Anthropologie 装饰蜡烛页至少要交回 3 条不重复的商品名称和详情链接,并保留 Decorative Candles、Price、Color、Brand 四组稳定文字。Moli 的主榜调用用时 6.07 秒,返回 13,028 个字符,形成 72 条字段完整、链接唯一的记录,四组文字全部命中。其他四款方案没有形成合格结果。": "The Anthropologie decorative-candle task requires at least 3 unique product names with detail links, plus the stable text Decorative Candles, Price, Color, and Brand. Moli's primary call takes 6.07 seconds and returns 13,028 characters, yielding 72 records with complete fields and unique links. All four text groups match. The other four methods do not produce acceptable results.", - "同一任务的两种观察": "Two observations of the same task", - "主榜调用": "Primary call", - "72 条完整记录": "72 complete records", - "达到数量、字段、去重和稳定文字要求。": "Meets count, field, uniqueness, and stable-text requirements.", - "后续三次诊断": "Three subsequent diagnostic calls", - "1 次成功,2 次为空": "1 success, 2 empty responses", - "确认结果有波动,尚不足以估计长期成功率。": "Establishes variability, but cannot estimate a long-term success rate.", - "这里测到的是列表结构与稳定内容,未冻结价格、库存和顺序。Moli 在 Anthropologie 的另一道退换货正文题也胜出,但同一站点的两题不能扩展成对所有电商站点的优势。后续应预先声明重复次数,再比较成功率与延迟分布。": "This checks list structure and stable content without freezing prices, stock, or ordering. Moli also wins the Anthropologie returns-text task, but two tasks from one site do not establish an advantage across e-commerce sites. Follow-up tests should declare repetition counts in advance, then compare success rates and latency distributions.", - "05 / 尚未解决的问题": "05 / Remaining gaps", - "普通正文与动态内容,还需要继续追赶": "General-page text and dynamic content still need work", - "普通网页的任务通过数接近,并不表示全文质量已经对齐。98 个任务具有完整、可比的正文参考,Chromium 的平均 Recall 为 99.8%,Moli 为 95.3%;全文 F1 分别为 76.1% 和 71.9%。": "Similar general-page pass counts do not mean equal full-text quality. Across 98 tasks with complete, comparable body references, Chromium's mean recall is 99.8% and Moli's is 95.3%; their full-text F1 scores are 76.1% and 71.9% respectively.", - "Moli 未通过的三个普通网页都是 W3C 长标准:CSS 2.1、WCAG 2.1、XML 1.0。三次返回同样的 475 字符安全验证页,Chromium 和 Lightpanda 则拿到了正文。针对这种输入,验证页识别和失败处理应成为接入测试的一部分,否则工具可能把一段非空错误提示当作正文交给模型。": "Moli's three general-page failures are long W3C standards: CSS 2.1, WCAG 2.1, and XML 1.0. Each returns the same 475-character security-verification page; Chromium and Lightpanda retrieve the body text. Integration tests should include verification-page detection and failure handling, or a tool may pass a nonempty error message to the model as body text.", - "动态网页:五款方案都停在 8/10": "Dynamic pages: all five methods score 8/10", - "两道共同未通过的任务,一道在 3 秒后显示引文列表,另一道将目标目录放在 iframe 子页面中。前者检查延迟内容是否被读到,后者检查正文获取是否进入了子页面。": "Of the two shared failures, one displays a quote list after 3 seconds and the other places the target catalog in an iframe. The first tests whether delayed content is read; the second whether retrieval reaches the embedded page.", - "延迟出现的引文列表": "Delayed quote list", - "Moli 返回了页面框架,没有交付目标列表。下一次实验需要比较不同等待条件,并检查实际出现的目标记录,不能只延长一个固定时间就判定已修复。": "Moli returns the page shell without the target list. The next experiment should compare wait conditions and inspect the actual target records. Extending a fixed delay alone does not establish a fix.", - "iframe 内的目录": "Catalog inside an iframe", - "Obscura 在这两题均未完成取页,说明相同失败结果不一定由相同原因造成。当前证据能定位缺失的交付物,但尚未通过受控实验分离网络、等待策略和 iframe 处理各自的影响。": "Obscura fails to complete retrieval on both tasks, so identical verdicts need not have identical causes. The evidence identifies missing outputs, but controlled experiments have not yet separated network, wait-strategy, and iframe effects.", - "06 / 结论与下一轮": "06 / Conclusions and next round", - "Moli 已有可用优势,下一步要验证稳定覆盖": "Moli has practical advantages; consistent coverage is the next test", - "对于同时读取网页、PDF 和媒体的 Agent,这些结果支持把 Moli 纳入实际接入测试。如果主要处理长 HTML 正文,Chromium 仍是重要的质量参照。输入中 PDF 的比例、是否需要原文件、是否经常遇到验证页和 iframe,会直接改变这组成绩对业务的意义。": "For agents reading pages, PDFs, and media, these results support including Moli in integration tests. For workloads dominated by long HTML text, Chromium remains an important quality reference. PDF frequency, original-file requirements, verification pages, and iframes determine how relevant these scores are to a workload.", - "查看公开版本记录": "View public release history", - "下一轮会扩大站点和文档样本,并对热门站点安排固定次数的重复运行。延迟列表、iframe、DOCX 和嵌入式查看器将作为重点回归对象,同时报告通过率、正文质量与相同任务上的速度,观察覆盖扩大后能否保持当前优势。": "The next round will expand site and document samples and run a fixed number of repetitions on popular sites. Delayed lists, iframes, DOCX, and embedded viewers will be priority regression cases. Pass rate, body quality, and same-task latency will show whether the current advantages hold as coverage expands.", - "Web Fetch 之外,我们还会用独立的 Browser Use 评测比较搜索、点击、翻页和填写等连续操作。届时关注的是 Agent 能否完成整段网页任务,以及过程中需要多少操作、等待和恢复。": "A separate Browser Use evaluation will compare sequences of searches, clicks, pagination, and form inputs. It will examine whether an agent completes the whole web task, and how many actions, waits, and recovery steps it needs.", - "完整报告、评测规则与核验记录": "Full report, evaluation rules, and review records", - "150 个 Web Fetch 任务实测:": "150 Web Fetch tasks tested:", - "五款浏览器,六类内容。把总成绩拆开,看看网页、文档和媒体文件是怎样拉开差距的。": "Five browsers, six content families. A closer look at how web pages, documents, and media files contribute to the results.", - "本轮比 Chromium 多完成": "More tasks completed than Chromium in this run:", - "同一任务要求": "Same task requirements", - "45 秒预算": "45-second budget", - "完整路线验收": "Complete-path acceptance", - "网页 → 可读正文": "Web → readable text", - "PDF → 文档文字": "PDF → document text", - "媒体 → 原始文件": "Media → original file", - "把同一个 PDF 链接交给五款浏览器,会得到什么?在本轮配置中,Moli 取回 PDF,由本地解析器提取出": "What happens when five browsers receive the same PDF URL? In this configuration, Moli fetched the PDF and a local parser extracted", - ",总用时": ", with a total latency of", - "。Chromium 的 DOM 转 Markdown 路线,在同一任务中得到的正文是 0 个字符。": ". Chromium's DOM-to-Markdown path produced 0 body characters on the same task.", - "这也是 Web Fetch 最实际的问题:": "This is the practical question behind Web Fetch:", - "输入一个网址,最终交给下游的内容是什么?": "Given a URL, what content reaches the next stage?", - "一篇文章需要正文,商品列表需要名称和链接,图片地址可能需要返回原文件。打开页面、收到响应,还只是中间步骤。": "An article needs body text; a product list needs names and links; an image URL may need to return the original file. Opening the page and receiving a response are intermediate steps.", - "01 / 总成绩": "01 / Overall results", - "10 题净差,全部来自文档与媒体": "The 10-task net difference comes entirely from documents and media", - "每道任务权重相同。条形长度表示通过任务数,未通过的任务也保留在分母中。": "Every task has equal weight. Bar length represents tasks passed; failed tasks remain in the denominator.", - "总榜中,Moli 完成 135 题。它与 Chromium 在普通网页、动态网页和热门站点组成的 120 题中,都完成了 111 题。最终差距来自另外 30 个文档、图片和视频任务。": "Moli completed 135 tasks overall. Across the 120 general-web, dynamic-web, and popular-site tasks, it and Chromium each completed 111. The overall difference comes from the remaining 30 document, image, and video tasks.", - "同样的总样本,拆成两种输入": "The same sample, split by input type", - "120 个任务 · 网页类": "120 tasks · web pages", - "两者完成数相同": "Equal numbers completed", - "30 个任务 · 文档与媒体": "30 tasks · documents and media", - "净差 +10 题": "Net difference: +10 tasks", - "网页类包括 100 个普通网页、10 个动态网页和 10 个热门站点任务;文档、图片和视频各 10 题。": "Web tasks include 100 general pages, 10 dynamic pages, and 10 popular-site tasks. Documents, images, and videos each contribute 10 tasks.", - "这 10 题是净差。两者共同通过 121 题,共同未通过 11 题;其余 18 题中,Moli 单独通过 14 题,Chromium 单独通过 4 题。这里的单独通过,即与另一款逐题对照时“仅本方法通过”。": "The 10-task difference is a net figure. Both passed 121 tasks and failed 11. Among the remaining 18 tasks, only Moli passed 14 and only Chromium passed 4. Here, 'only' refers to this pairwise comparison.", - "共同通过": "Both passed", - "Moli 单独通过": "Only Moli passed", - "Chromium 单独通过": "Only Chromium passed", - "共同未通过": "Neither passed", - "按内容类型看,Moli 在文档、图片、视频中分别多通过 6、3、1 题。网页类内部也有差异:热门站点多 2 题,普通网页少 2 题,动态网页打平,合计抵消。": "By content family, Moli passed 6 more document tasks, 3 more image tasks, and 1 more video task. Within web tasks, 2 additional popular-site passes offset 2 fewer general-web passes; dynamic-web results were tied.", - "每题等权。速度和全文指标不计入任务通过率;普通网页占总样本的三分之二。": "Tasks are equally weighted. Latency and full-text metrics do not contribute to the task pass rate; general web pages make up two-thirds of the sample.", - "普通网页": "General web", - "动态网页": "Dynamic web", - "热门站点": "Popular sites", - "文档": "Documents", - "02 / 文档": "02 / Documents", - "PDF 要交付正文,先要取得完整文件": "Delivering PDF text starts with fetching the complete file", - "案例:Attention Is All You Need · PDF": "Example: Attention Is All You Need · PDF", - "Moli 获取 PDF": "Moli fetches the PDF", - "本地 pypdf 解析": "Local pypdf parsing", - "参考正文分区全部覆盖": "All reference body regions covered", - "本题词级 precision / recall / F1": "Word-level precision / recall / F1 for this task", - "Chromium 本轮路线的正文输出": "Body output from Chromium's tested path", - "图示为本轮预配置的处理流程。Moli 负责获取文件,pypdf 负责解析;并非 Moli 自行解析 PDF,也未测试未知 URL 的自动类型识别。": "This diagram shows the preconfigured path used in this run. Moli retrieves the file and pypdf parses it. Moli does not itself parse the PDF here, and automatic type detection for unknown URLs was not tested.", - "同一题中,Lightpanda 得到 138 个字符;Obscura 和 Kitesurf 把部分 PDF 字节转成了不可读文本。Moli 的价值体现在把完整文件送到了解析阶段,让这条路线最终形成可用正文。": "On the same task, Lightpanda produced 138 characters; Obscura and Kitesurf converted some PDF bytes into unreadable text. Moli delivered the complete file to the parser, allowing this path to produce usable body text.", - "给 Chromium 增加文件下载和同类解析器后,比较对象就变成了另一条路线。本轮也没有覆盖所有文档:Moli 在直接 DOCX 和两个嵌入式文档查看页中仍未交付合格正文。": "Adding file download and an equivalent parser to Chromium would create a different path to evaluate. Document coverage was also incomplete: Moli did not deliver acceptable text for a direct DOCX file or two embedded document viewers.", - "03 / 图片与视频": "03 / Images and videos", - "有些任务需要的,就是原文件": "Some tasks need the original file", - "图片和视频任务分两类:从详情页找出目标资源地址,或者直接取得文件本身。后一类要检查文件格式、长度和哈希,确认交回的字节与目标文件一致。": "Image and video tasks either locate a resource URL on a detail page or retrieve the file itself. The latter checks file format, length, and hash to confirm that the returned bytes match the target file.", - "图片原文件": "Original image file", - "视频原文件": "Original video file", - "✓ 格式、长度与哈希通过": "✓ Format, length, and hash match", - "文件校验只证明正确文件已到达客户端,不评价图片识别、视频理解或内容质量。": "File validation only establishes that the correct file reached the client. It does not assess image recognition, video understanding, or content quality.", - "以直接 PNG 为例,Moli 返回了原始图片;Chromium 和 Lightpanda 的当前路线没有返回原文件,另外两款输出了不可用文本。这些字节可以继续送入 OCR、视觉模型、转码或存档流程。": "For the direct PNG task, Moli returned the original image. The current Chromium and Lightpanda paths did not return the file, while the other two produced unusable text. These bytes can feed OCR, vision models, transcoding, or archival workflows.", - "在图片题中,Moli 相对 Chromium 多通过 3 题;视频题净多 1 题。不过,Blender 下载资源题由 Chromium、Lightpanda 和 Obscura 完成,Moli 没有找到目标资源。": "Moli passed 3 more image tasks than Chromium and a net 1 more video task. However, Chromium, Lightpanda, and Obscura completed the Blender download-resource task, where Moli did not find the required resource.", - "直接文件获取和网页内资源发现,仍是两项不同的能力。": "Direct file retrieval and resource discovery within a page remain distinct capabilities.", - "04 / 网页": "04 / Web pages", - "网页总数打平,具体页面各有得失": "Web totals tie, with different wins on individual pages", - "120 个网页类任务中,Moli 和 Chromium 各有 3 道对方未完成的任务。Moli 完成了 Anthropologie 的退换货正文、装饰蜡烛列表,以及 Reddit 编程社区列表;Chromium 完成了 CSS 2.1、WCAG 2.1 和 XML 1.0 三篇 W3C 标准。": "Across 120 web tasks, Moli and Chromium each completed 3 tasks the other did not. Moli completed Anthropologie's returns page and decorative-candle list, plus the Reddit programming-community list. Chromium completed the CSS 2.1, WCAG 2.1, and XML 1.0 W3C standards.", - "一张商品列表,实际交回了什么?": "What did the product-list task actually return?", - "Anthropologie 装饰蜡烛页要求至少返回 3 条不重复的商品名称和详情链接,并保留四组稳定的页面文字。Moli 的主榜调用形成了 72 条字段完整且唯一的记录。": "The Anthropologie decorative-candle task requires at least 3 unique product names with detail links, plus four groups of stable page text. Moli's primary call produced 72 complete, unique records.", - "完整且不重复的名称与详情链接": "Complete, unique names and detail links", - "四组稳定文字全部命中。": "All four stable-text groups matched.", - "验收的是名称、链接、数量和结构;没有冻结商品价格、库存、排序,也不证明信息始终最新。": "Acceptance checks names, links, count, and structure. Prices, stock, and ordering were not frozen, and the check does not prove continuous freshness.", - "另外四款浏览器在这一题中没有形成合格记录。但后续三次诊断调用里,Moli 只有一次成功,另外两次为空响应。这说明结果有波动,还不能估计长期成功率或确定波动原因。同一站点贡献了两道单边成功题,也不足以概括电商网站的普遍表现。": "The other four browsers did not produce acceptable records on this task. In three later diagnostic calls, however, Moli succeeded once and returned empty responses twice. This establishes variability, but not its cause or a long-term success rate. Two exclusive successes also came from the same site, which is insufficient to generalize across e-commerce sites.", - "普通正文的完整度,Chromium 仍更高": "Chromium still achieves higher general-page body coverage", - "普通网页中,Chromium 完成 99/100,Moli 为 97/100。对具有完整可比参考的正文继续核对,Chromium 找回的参考文字更多,全文 F1 也更高。": "Chromium completed 99/100 general-web tasks, compared with Moli's 97/100. On tasks with complete, comparable references, Chromium recovered more reference text and achieved higher full-text F1.", - "98 个具有完整可比参考的普通网页,取逐题算术平均,包含已测失败项。文字重合不等于事实准确率。": "Arithmetic means across 98 general-web tasks with complete, comparable references, including measured failures. Text overlap is not factual accuracy.", - "全文指标": "Full-text metric", - "参考正文找回比例 · Recall": "Reference text recovered · Recall", - "输出文字纯净度 · Precision": "Output text precision · Precision", - "综合文字重合 · F1": "Combined text overlap · F1", - "Recall 看参考正文找回了多少;Precision 看输出里有多少文字落在参考范围内;F1 综合两者。导航、页脚和排版差异也会影响这些指标。": "Recall measures how much reference text was recovered; precision measures how much output text falls within the reference scope; F1 combines them. Navigation, footers, and formatting differences also affect these metrics.", - "Moli:停在验证页": "Moli: stopped at verification", - "三篇 W3C 标准均返回同样的 475 字符安全验证页面,没有取得目标正文。": "All three W3C standards returned the same 475-character security-verification page rather than the required body text.", - "Chromium / Lightpanda:取得正文": "Chromium / Lightpanda: body retrieved", - "两条路线在这些长标准页面完成了任务。请求成功与正文可用需要分别判断。": "Both paths completed these long-standard tasks. Request success and usable body text must be checked separately.", - "动态网页的两处共同空缺": "Two shared gaps on dynamic pages", - "五款浏览器均为 8/10。共同未通过的两题,一题在 3 秒后显示引文列表,另一题把目标目录放在 iframe 中。": "All five browsers scored 8/10. Of the two shared failures, one displays a quote list after 3 seconds and the other places the required catalog in an iframe.", - "延迟内容": "Delayed content", - "Moli 返回了页面框架,没有交付随后出现的目标列表。后续需要验证按目标内容等待是否有效。": "Moli returned the page shell without the target list that appeared later. A follow-up experiment should test whether waiting for the target content helps.", - "iframe 子页面": "iframe content", - "共同失败并不意味着原因相同。Lightpanda 在 IMDb、Kitesurf 在 YouTube 也各有其余四款未完成的任务。总榜排序,不能代替对具体页面的测试。": "Shared failures do not establish a shared cause. Lightpanda also had an IMDb success, and Kitesurf a YouTube success, that the other four did not achieve. Overall rankings cannot replace testing individual pages.", - "05 / 速度观察": "05 / Latency observations", - "共同成功的 8 题中,Moli 中位耗时 2.70 秒": "Across 8 shared successes, Moli's median latency is 2.70 seconds", - "速度比较另用一组预先选定的普通网页,并将范围收窄到全部受测路线共同完成的任务,避免拿“各自成功的不同网页”直接比较。": "Latency uses a separately preselected set of general pages, narrowed to tasks completed by every tested path. This avoids directly comparing different successful subsets.", - "相同成功任务的 P50 · 越短越快": "P50 on identical successful tasks · shorter is faster", - "预先选定 20 个普通网页,全部 7 条受测路线共同通过 8 个。图中只展示本文五款浏览器在这组相同任务上的 P50;P50 表示一半任务的耗时不超过该值。": "Of 20 preselected general pages, all 7 tested paths passed 8. The chart shows P50 for this article's five browsers on that identical subset; P50 means half the tasks took no longer than that value.", - "06 / 如何理解这份成绩": "06 / Interpreting the results", - "输入内容的构成,决定这些优势有多大": "Your input mix determines how much these advantages matter", - "如果工作主要是提取普通 HTML 正文,而且看重完整覆盖,Chromium 在本轮中仍是重要参照。它多完成了 2 个普通网页任务,正文找回比例和 F1 也更高。": "For work focused on general HTML body extraction and complete coverage, Chromium remains an important reference in this run. It completed 2 more general-web tasks and achieved higher body recall and F1.", - "Moli 在本轮的领先,来自更多内容类型最终形成了可用交付物。": "Moli's lead in this run comes from turning more content types into usable outputs.", - "接入时,可以按自己的 URL 构成重新抽样:PDF 占比、是否需要原文件、是否常遇到验证页和 iframe,都会改变这 10 题差距对业务的意义。正文、列表和文件也各自需要对应的结果校验。": "For adoption, resample according to your own URL mix. PDF frequency, original-file requirements, verification pages, and iframes all change what the 10-task difference means for your workload. Body text, lists, and files also need their own output validation.", - "后续评测将重点扩充样本,并补充热门站点的重复运行、DOCX、嵌入式查看器、延迟列表和 iframe 测试。搜索、点击、翻页和表单填写则需要独立的 Browser Use 评测,检验浏览器真正执行网页操作的表现。": "Future evaluation will expand the sample and add repeated popular-site runs, DOCX, embedded viewers, delayed lists, and iframe tests. Search, clicks, pagination, and form filling require a separate Browser Use evaluation of actual web interactions.", - "关键原始案例:": "Key original cases:", - "PDF 正文": "PDF body text", - "PNG 原文件": "Original PNG", - "商品列表": "Product list", - "完整评测、任务规则与核验记录 ↗": "Full benchmark, task contracts, and review records ↗", - "本文计算明细": "Article calculation details", - "增加五浏览器 Web Fetch 图文解读": "Add an illustrated five-browser Web Fetch interpretation", - "文章范围": "Article scope", - "结论依据": "Basis for conclusions", - "总通过数不能说明差距来自何种输入。": "The total pass count does not explain which input types contribute to the difference.", - "展示 Moli 与 Chromium 在 120 个网页任务中同为 111 题,30 个文档与媒体任务分别完成 24、14 题。": "Show that, across 120 web tasks, Moli and Chromium each complete 111; across 30 document and media tasks, they complete 24 and 14 respectively.", - "PDF 结论包含本地解析器;文件校验不测媒体语义;8 题共同成功样本的速度只作探索性观察。": "PDF conclusions include a local parser; file validation does not measure media semantics; latency on the 8 shared successes is exploratory only.", - "阅读格式": "Reading format", - "报告表格不便直接用于长文阅读。": "Report tables were not convenient for long-form reading.", - "集成文章和独立 HTML 共用正文、内联图形与样式,适配手机和打印。": "The integrated article and standalone HTML share body text, inline graphics, and styles, with mobile and print layouts.", - "仅更新展示与解释,无新增供应商调用,无分数或耗时更改。": "Presentation and explanation changes only: no new provider calls and no score or latency changes.", - "公共文件另以独立 HTTP 字节链路验证 3/3 次目标哈希匹配,用于确认源文件与统一字节验收链路可用。该对照不是参评方法,不进入排名,也不替代任何 provider 的调用。": "Public files were also checked through an independent HTTP byte path, with the target hash matching in 3/3 trials. This confirms that the source files and shared byte-validation path work. The control is not an evaluated method, does not enter the ranking, and does not replace any provider call.", - "本页从有完整审计依据的修订开始记录。更早的站点版本仍保留在 Artifact Hub,但没有逐项变更说明,故不补编历史。": "This page begins with revisions that have a complete audit basis. Earlier site versions remain available in Artifact Hub, but no itemized history is reconstructed where none was recorded.", - "Web Fetch · 实测报告": "Web Fetch · Measured benchmark", - "Web Fetch · 浏览器路线实测": "Web Fetch · Browser-method benchmark", - "评测报告": "Benchmark report", - "报告解读": "Interpretation", - "报告版本说明": "Changelog", - "评测集": "Dataset", - "评测方法": "Methodology", - "结果分析": "Analysis", - "核验记录": "Review", - "方法介绍": "Methods", - "环境与复现": "Reproduction", - "账号与安装": "Access & setup", - "切换主题": "Toggle theme", - "序号": "No.", - "方法": "Method", - "计划调用": "Planned calls", - "通过率": "Pass rate", - "全调用 P50 ms": "All-call P50 (ms)", - "全调用 P95 ms": "All-call P95 (ms)", - "全调用均值": "All-call mean", - "全调用 P50": "All-call P50", - "全调用 P95": "All-call P95", - "全调用均值 / P50 / P95": "All-call mean / P50 / P95", - "总耗时均值": "Mean total latency", - "总耗时 P50": "P50 total latency", - "总耗时 P95": "P95 total latency", - "总耗时均值 / P50 / P95": "Mean / P50 / P95 total latency", - "总耗时按每题所有物理调用累计,包含失败重试和重试等待;每题仍只计一个样本。": "Total latency accumulates all physical calls for each task, including failure retries and retry waits; each task remains one sample.", - "按需查看共同成功任务耗时": "View shared-pass latency on demand", - "通过调用 P50 ms": "Successful-call P50 (ms)", - "通过调用 P95 ms": "Successful-call P95 (ms)", - "类型": "Family", - "结果": "Result", - "任务通过": "Pass", - "任务失败": "Fail", - "任务": "Task", - "任务 ID": "Task ID", - "运行 ID": "Run ID", - "请求": "Request", - "请求成功": "Succeeded", - "成功": "Succeeded", - "失败": "Failed", - "通过": "Pass", - "未通过": "Fail", - "未测": "Not measured", - "达到目标": "Meets target", - "未达到硬下限": "Below hard floor", - "低于目标,但达到硬下限": "Below target; above hard floor", - "命中": "Matched", - "未命中": "Not matched", - "缺失": "Missing", - "判定": "Verdict", - "结论": "Conclusion", - "依据": "Evidence", - "目标": "Requirement", - "项目": "Field", - "值": "Value", - "耗时": "Latency", - "均值": "Mean", - "完整度": "Recall", - "纯净度": "Precision", - "覆盖率": "Coverage", - "实际命中": "Observed match", - "标准答案": "Reference answer", - "完整标准答案": "Complete reference answer", - "目标文件": "Required file", - "实际返回": "Actual output", - "实际返回证据": "Actual-output evidence", - "执行记录": "Execution record", - "评分方式": "Scoring method", - "保存输出离线评分": "Offline scoring of the saved output", - "评分时间": "Scored at", - "开始时间(UTC)": "Started (UTC)", - "供应商请求 ID": "Provider request ID", - "HTTP 状态": "HTTP status", - "输出字符": "Output characters", - "输出校验值": "Output checksum", - "评分实现 SHA-256": "Scorer SHA-256", - "任务结果": "Task result", - "正文是否抓全": "Body coverage", - "标准答案位置": "Reference region", - "标准片段数": "Reference shingles", - "命中片段数": "Matched shingles", - "为什么这样判": "Why this verdict", - "运行与评分信息": "Run and scoring details", - "标题文字质量": "Title-text quality", - "浏览器 HTML 证据": "Browser HTML evidence", - "原始文档证据": "Original-document evidence", - "原始响应证据": "Original-response evidence", - "原始文件字节证据": "Original-file byte evidence", - "接口返回的附加字段": "Additional provider fields", - "查看完整运行、哈希与评分字段": "View complete run, hashes, and scoring fields", - "查看判定使用的标准答案": "View the reference used for scoring", - "查看本次完整实际返回": "View the complete saved output", - "查看逐项判定": "View item-level verdicts", - "标准答案与实际返回": "Reference and actual output", - "下载原始输出": "Download raw output", - "下载完整标准答案": "Download complete reference", - "下载标准答案": "Download reference", - "查看来源记录": "View source record", - "打开源页 ↗": "Open source page ↗", - "源页 ↗": "Source page ↗", - "查看该任务的全部方法": "View all methods for this task", - "下载转换前的完整 DOM HTML": "Download the complete pre-conversion DOM HTML", - "查看各位置的完整标准正文和人工定位样例": "View complete reference regions and inspection snippets", - "这一位置的完整标准正文": "Complete reference text for this region", - "实际输出中的定位样例(仅供人工查看)": "Matching snippet from the actual output (inspection only)", - "全部方法执行结果": "Results for all methods", - "评分核对点": "Scoring checks", - "标准答案与来源": "Reference and source", - "本任务的标准答案 / 目标结果": "Reference answer / required deliverable", - "标准答案与验收规则": "Reference and acceptance contract", - "任务要求": "Task requirement", - "结构要求": "Structure requirements", - "结构结果:": "Structure result:", - "结构判定": "Structure verdict", - "结构类型": "Structure type", - "稳定内容检查": "Stable-content check", - "稳定内容核对点": "Stable-content checks", - "条目结构检查": "Record-structure check", - "条目字段": "Record field", - "检出条目数": "Records found", - "完整条目数": "Complete records", - "完整且唯一条目数": "Complete unique records", - "声明最少条目数": "Declared minimum records", - "声明最多条目数": "Declared maximum records", - "查看机器可读契约": "View machine-readable contract", - "查看解析出的全部记录和机器检查字段": "View all parsed records and machine-check fields", - "失败分类": "Failure class", - "发生阶段": "Stage", - "判定依据": "Decision basis", - "归因边界": "Attribution boundary", - "查看失败分类与归因边界": "View failure class and attribution boundary", - "请求成功率": "Request success rate", - "综合任务通过率": "Overall task pass rate", - "任务通过率": "Task pass rate", - "判分完整性": "Judgement coverage", - "全文完整度 / 纯净度 / F1": "Full-text recall / precision / F1", - "完整度 / 纯净度 / F1": "Recall / precision / F1", - "文字核对覆盖": "Required-text coverage", - "资源命中率": "Resource match rate", - "文件取得率": "File delivery rate", - "信息通过率": "Metadata pass rate", - "静态网站正文": "General web text", - "动态网站正文": "Rendered web text", - "热门网站正文": "Popular-site text", - "文档正文": "Document text", - "图片获取": "Image retrieval", - "视频获取": "Video retrieval", - "六类任务概览": "Six task families", - "综合通过率总榜": "Overall coverage ranking", - "六类分榜": "Rankings by task family", - "总榜指标说明": "Overall-ranking metrics", - "同题耗时 · 全部方法共同通过子集": "Matched-task latency · tasks passed by every method", - "同题均值": "Matched mean", - "同题 P50": "Matched P50", - "方法组合对比": "Method comparison", - "全部执行记录": "All execution records", - "账号与获取入口": "Accounts and official sources", - "本轮方法清单": "Methods in this benchmark", - "账号要求": "Account requirement", - "官方获取入口": "Official source", - "变量名称": "Variable names", - "本轮配置": "Measured configuration", - "公开内容边界": "Public-data boundary", - "代码与评测集": "Code and dataset", - "公共条件": "Shared conditions", - "运行时版本与 SHA-256": "Runtime versions and SHA-256", - "发布与加载": "Publishing and loading", - "核心复现指令": "Core reproduction commands", - "当前版本": "Current release", - "历史版本": "Earlier releases", - "范围与下载": "Scope and downloads", - "独立文件源对照": "Independent file-source control", - "本版结果影响": "Result impact in this release", - "文件任务": "File tasks", - "整份报告": "Full report", - "核验轮次": "Review rounds", - "未完成:": "Outstanding:", - "范围:": "Scope:", - "发现:": "Finding:", - "纠正:": "Correction:", - "查看更多": "View more", - "全部": "All", - "正文范围不完整": "Incomplete body coverage", - "供应商声明正文已截断": "The provider declares that the body was truncated", - "供应商输出中精确命中的截断声明;完整正文任务一律不通过,资源与结构任务仍按各自交付合同验收。": "Exact provider-authored truncation notices found in the output. A complete-body task fails; resource and structure tasks continue to follow their own deliverable contracts.", - "输出文本解码损坏": "Corrupted text decoding", - "缺少目标资源": "Required resource missing", - "缺少关键信息": "Required information missing", - "完全空返回": "Completely empty response", - "只返回等待提示或页面外壳": "Waiting message or page shell only", - "规则不满足": "Acceptance rules not met", - "请求或程序执行失败": "Request or program execution failed", - "超过端到端预算": "End-to-end deadline exceeded", - "接口鉴权失败": "API authentication failed", - "源站拒绝该次访问": "Origin denied this request", - "源站拒绝访问": "Origin denied access", - "私有网络安全拦截": "Private-network safety block", - "Moli原始输出为空": "Empty Moli stdout", - "仅返回页面壳或加载占位": "Page shell or loading placeholder only", - "页面身份不匹配": "Page identity mismatch", - "完整条目数量不足": "Too few complete records", - "缺少稳定文字或必要字段": "Missing stable text or required fields", - "正文覆盖不足": "Insufficient body coverage", - "源站访问": "Origin access", - "地址解析与安全检查": "Address resolution and safety check", - "输出交付": "Output delivery", - "内容提取": "Content extraction", - "页面核验": "Page verification", - "条目核验": "Record verification", - "内容核验": "Content verification", - "正文核验": "Body verification", - "目标网站明确拒绝本次访问;没有取得正常页面正文。": "The target site explicitly denied this access attempt; no normal page body was obtained.", - "域名被解析为特殊地址,统一启用的私有网络安全保护在网页请求发出前停止访问;地址来源未证实。": "The domain resolved to a special-use address, and the uniformly enabled private-network safety guard stopped access before a page request was sent; the source of that address remains unverified.", - "Moli进程完成,但标准输出为0字节;原始捕获、保存文件和评分输入一致,没有发生测试程序漏读或截断。": "The Moli process completed with zero bytes on stdout; the raw capture, saved artifact, and scoring input agree, with no test-side omission or truncation.", - "Moli返回了非空内容,但只有页面外壳、加载提示或不可见占位符,没有交付目标正文。": "Moli returned non-empty content, but it contained only a page shell, loading message, or invisible placeholder rather than the target body.", - "Moli返回了内容,但最终页面或页面语言没有与冻结目标一致。": "Moli returned content, but the final page or page language did not match the frozen target.", - "Moli返回了页面内容,但同一条目内字段完整且不重复的记录少于冻结门槛。": "Moli returned page content, but the number of deduplicated records with complete fields from the same item was below the frozen threshold.", - "Moli返回了部分页面内容,但缺少冻结规则要求的稳定文字或必要字段。": "Moli returned part of the page, but stable text or required fields from the frozen rules were missing.", - "稳定页面内容已经命中,但冻结正文参考仍有一个或多个区域未达到覆盖门槛。": "Stable page content matched, but one or more frozen body-reference regions remained below the coverage threshold.", - "服务取页失败": "Provider fetch failed", - "接口是否正常返回;是否取得目标内容另看任务结果。": "Whether the interface returned normally. Delivery of the requested content is judged separately.", - "已记录的 HTTP 状态码;空值表示接口未提供,不据此推断成功。": "Recorded HTTP status. A blank means the interface did not provide one; success is not inferred from it.", - "该次保存的完整输出字符数量,不代表有效正文量。": "Character count of the complete saved output; this is not a measure of useful body text.", - "输出文件的 SHA-256,用于检查文件内容是否改变。": "SHA-256 of the saved output, used to detect content changes.", - "参考正文中有多少被取回;越高越少遗漏。": "Share of the reference text recovered; higher means fewer omissions.", - "返回文字中有多少属于参考正文;越高越少杂文。": "Share of returned text that belongs to the reference; higher means less unrelated text.", - "“—”表示不适用、缺少参考或没有可计算的样本,不等于 0。": "“—” means not applicable, no reference, or no computable sample; it does not mean 0.", - "左边是判定时使用的标准答案或明确验收规则,右边是该次调用原样保存的完整输出。两栏都是真实内容,不是摘要;长内容可在栏内滚动。": "The left side is the reference or explicit acceptance contract used for the verdict. The right side is the complete output saved from this call. Neither side is a summary; long content scrolls within its pane.", - "下方直接展示用于判定的完整内容;长内容可在栏内滚动。": "The complete material used for the verdict appears below; long content scrolls within its pane.", - "这是同一份标准答案在该位置的原文,不是独立任务。": "This is the text from the same reference at this position, not a separate task.", - "这是从完整实际返回中选出的一小段,便于确认文字确实出现;它不是覆盖率计算所使用的全部实际输出。": "This short excerpt is selected from the complete output so the match can be inspected. Coverage is calculated against the complete output, not this excerpt.", - "实际返回中未找到可供定位的连续原文。": "No continuous source excerpt could be located in the actual output.", - "没有可供人工定位的连续文字样例;机器覆盖率仍按上方完整实际返回计算。": "No continuous inspection snippet is available; machine coverage is still calculated from the complete output above.", - "正文核对点通过;完整程度另见全文指标。": "Required body-text checks passed; full-text coverage is reported separately.", - "这里直接列出本任务真正要求的文字和记录数量,不要求用户阅读 JSON。": "The required text and record counts are shown directly; readers do not need to inspect JSON.", - "本次任务按稳定内容与条目结构共同判定。全文指标须有完整、可比的参考,不能由条目数量推算。": "This task is judged jointly on stable content and record structure. Full-text metrics require a complete comparable reference and cannot be inferred from record counts.", - "该分数只描述已返回标题的文字质量,不参与本任务通过判定。": "This score describes only the text quality of returned titles and does not determine task acceptance.", - "完整报告中的任务均明确判为通过或失败。诊断构建可显示未测,但会被标为不可靠且不能发布。通过不证明事实准确性、实时性或整页完整性。": "Every task in a complete report has an explicit pass or fail verdict. Diagnostic builds may show unmeasured tasks, but are marked unreliable and cannot be published. A pass does not prove factual accuracy, freshness, or whole-page completeness.", - "所有分数由本轮调用记录计算。网页与服务可能变化;结论仅适用于本轮样本和配置。": "All scores are computed from the recorded calls. Web pages and services may change; conclusions apply only to this sample and configuration.", - "先比较方法在同一任务上的成败组合,再筛选全部执行记录。": "Compare methods on the same tasks first, then filter the complete execution record.", - "按同一任务逐项比较,不用各方法不同的成功样本拼接结论。成功与失败均沿用主榜的程序判定;点击方法可查看标准答案、实际返回和原因。": "Methods are compared task by task; conclusions never splice together different success samples. Pass and fail states use the primary program verdicts. Open a method to inspect its reference, output, and rationale.", - "记录每轮核验的范围、发现、纠正和未决事项。": "Each review round records its scope, findings, corrections, and unresolved items.", - "本轮没有绑定独立核验记录。": "No independent review record is bound to this build.", - "代码、评测集和复现说明以 browser-eval 仓库为准;本页只列本轮必要配置。": "The browser-eval repository is authoritative for code, dataset, and reproduction instructions. This page lists only the configuration required for this run.", - "这里列的是完成本报告所需的全部外部准备:账号、官方获取入口和变量名称。变量值、本机路径、登录会话、Cookie 和 Token 均不进入报告。": "This is the complete external setup required for the benchmark: accounts, official sources, and variable names. Values, local paths, login sessions, cookies, and tokens are never included.", - "报告只公开复现所需的名称和入口,不公开凭据值。": "The report publishes only the names and official entry points needed for reproduction, never credential values.", -} +_TRANSLATIONS = json.loads(Path(__file__).with_name("report-translations.json").read_text(encoding="utf-8")) + +TEXT = dict(_TRANSLATIONS['TEXT']) # These strings are presentation copy shared by the overview, methodology, # provider, review, release-note, and reproduction pages. Keeping them here # (rather than maintaining a second set of report builders) is intentional: # both languages still render the same measured rows and evidence objects. -TEXT.update({ - "Moli Web Fetch 实测:五款浏览器,150 道任务": "Moli Web Fetch benchmark: five browsers, 150 tasks", - # Report chrome and common controls. - "评测报告 · Web Fetch": "Benchmark report · Web Fetch", - "报告解读 · Web Fetch": "Interpretation · Web Fetch", - "报告版本说明 · Web Fetch": "Changelog · Web Fetch", - "评测集 · Web Fetch": "Dataset · Web Fetch", - "评测方法 · Web Fetch": "Methodology · Web Fetch", - "结果分析 · Web Fetch": "Analysis · Web Fetch", - "核验记录 · Web Fetch": "Review · Web Fetch", - "独立结果核验": "Independent result review", - "独立核验结论": "Independent review verdict", - "直接证据": "Direct evidence", - "最终结论": "Final verdict", - "分歧裁定": "Disagreement adjudication", - "核验记录": "Review record", - "方法介绍 · Web Fetch": "Methods · Web Fetch", - "环境与复现 · Web Fetch": "Reproduction · Web Fetch", - "账号与安装 · Web Fetch": "Access & setup · Web Fetch", - "中文": "中文", - "案例": "Task", - "来源": "Source", - "类别": "Family", - "参考状态": "Reference status", - "达标率": "Pass rate", - "核对依据": "Reference basis", - "原访问": "Original access", - "详情链接": "Details", - "核对点": "Check", - "原因": "Reason", - "有效": "Valid", - "受限": "Access restricted", - "未提供完整参考": "No complete reference", - "完整正文": "Complete body reference", - "资源清单": "Resource manifest", - "原文件": "Original file", - "文字信息": "Text metadata", - "输入": "Input", - "输出": "Output", - "调用输入示例": "Example request", - "示例 JSON": "Example JSON", - "本轮未执行": "Not run in this benchmark", - "方法资料": "Method resources", - "价格与计费 →": "Pricing & billing →", - "接口与示例 →": "Interfaces & examples →", - "文档 / 源码 ↗": "Documentation / source ↗", - "官方定价 ↗": "Official pricing ↗", - "官方资格与条款 ↗": "Official eligibility and terms ↗", - "注册 / 登录": "Register / sign in", - "获取凭据": "Get credentials", - "平台": "Platform", - "超时秒": "Timeout (seconds)", - "并发方式": "Concurrency model", - "参数": "Parameter", - "版本": "Version", - "身份依据": "Identity evidence", - "部署路径": "Deployment", - "调用数": "Calls", - "本轮配置:": "Configuration:", - "对应接口:": "Interface:", - "版本未记录": "Version not recorded", - "托管后端未公开版本": "Managed backend version not disclosed", - "无本地二进制": "No local binary", - "运行时版本与 SHA-256": "Runtime version and SHA-256", - "运行时 SHA-256;nightly 源修订不可得": "Runtime SHA-256; the nightly source revision is unavailable", - "事后与官方归档同哈希;原调用未记版本字符串": "Post-run hash matches the official archive; the original call did not record a version string", - "测试适配器": "Test adapter", - "已纳入上述样本。": "Included in the sample above.", - - # Overview and ranking guidance. - "Web Fetch 评测报告": "Web Fetch benchmark report", - "五类浏览器、六种配置、七类任务:正文、媒体与交付边界": "Five browsers, six configurations, seven task families: content, media, and delivery boundaries", - "从分数差异、失败案例和实现机制,理解如何选择抓取方法。": "Use score differences, failure evidence, and implementation boundaries to choose the right retrieval method.", - "阅读全文 →": "Read the interpretation →", - "查看核验轮次 →": "View review rounds →", - "调用完整:": "Result coverage:", - "只在同一任务类别内比较。": "Compare results only within the same task family.", - "本类最高:": "Best in family:", - "总榜口径:": "Overall-ranking basis:", - "150 个任务中满足各自验收规则的数量;每题等权。": "Number of the 150 tasks that satisfy their own acceptance contract; every task has equal weight.", - "六类通过数": "Passes by task family", - "各分榜通过数 / 本类任务数,用于定位总分来自哪些能力。": "Passes divided by tasks in each family, showing which capabilities contribute to the overall result.", - "审查结果": "Review coverage", - "独立盲审覆盖数、审查方法和未决数。": "Coverage of the independent blind review, its protocol, and unresolved count.", - "分榜用于判断具体能力。默认按任务通过率排序,点击列名可切换;质量分数与通过率分别计算。": "Family rankings show specific capabilities. They default to task pass rate; select a column heading to sort. Text quality and pass rate are calculated separately.", - "任务例子与评测边界": "Example and evaluation boundary", - "一个例子": "Example", - "范围与边界": "Scope and limits", - "怎样才算任务通过?": "What counts as a pass?", - "典型处理流程": "Typical retrieval path", - "任务示例与完整判定规则 →": "Examples and complete acceptance rules →", - "案例与参考 →": "Tasks and references →", - "查看案例 →": "View task →", - "指标说明 · 通过率、质量与耗时": "Metric definitions · pass rate, quality, and latency", - "表格可横向滚动查看全部列。": "Scroll horizontally to view every column.", - "标题文字质量 · 计算过程与结果": "Title-text quality · method and results", - "识别返回条目": "Identify returned records", - "按详情链接对齐参考": "Join references by detail URL", - "逐标题比较文字": "Compare title text", - "汇总可比结果": "Aggregate comparable results", - "标题完整度": "Title recall", - "标题纯净度": "Title precision", - "标题参考覆盖(可评分 / 返回)": "Title reference coverage (scorable / returned)", - "初始 HTML": "Initial HTML", - "渲染正文": "Rendered body text", - "配对证据校验": "Paired-evidence validation", - "任务结果与耗时进入主榜;全文指标无完整参考时显示 —。": "Task verdict and latency enter the primary ranking; full-text metrics are shown as — without a complete reference.", - "资源": "Resource", - - # Shared metric definitions and task-family explanations. - "静态网站正文、动态网站正文、热门网站正文、风控网页正文和文档正文使用同一评分流程。页面来源与加载方式不同,改变的是案例要求,不改变指标定义。": "Static-site, dynamic-site, popular-site, risk-control, and document body-text tasks share one scoring pipeline. Their sources and loading behavior affect case requirements, not metric definitions.", - "统一验收流程": "Shared acceptance pipeline", - "接口返回 → 排除空内容与错误页 → 核对题目要求的文字和字段 → 检查列表数量、类型与唯一性 → 汇总任务结果。只执行该例适用的检查;失败详情保留具体未满足项。图片与视频沿用请求和交付检查,资源地址或文件字节另按格式核验。": "Provider response → reject empty content and error pages → check required text and fields → validate record count, types, and uniqueness → determine the task verdict. Only applicable checks run for each task, and failures retain the unmet requirements. Image and video tasks use the same request and delivery checks, with resource URLs or file bytes validated by format.", - "满足该例全部验收规则的次数 ÷ 已有验收证据的任务。段落、列表、字段都属于内容要求;没有 F1 最低分门槛。请求失败计入分母;任何计划调用缺失或无法判分时,整份报告标为不可靠并禁止发布。": "Calls satisfying every acceptance rule divided by tasks with acceptance evidence. Paragraphs, lists, and fields are all content requirements; there is no minimum F1 threshold. Request failures remain in the denominator. A missing or unjudgeable planned call makes the report unreliable and blocks publication.", - "每例命中的必需文字组数 ÷ 登记组数,再按案例取均值。固定标题、说明、字段标签或事实句均可作为核对组;易变名单和价格不固定为答案。没有文字依据的案例不记作满分,覆盖案例数另列。": "Required text groups matched divided by registered groups, averaged by task. Stable headings, descriptions, field labels, and factual statements may form a check group; volatile lists and prices are not frozen as answers. Tasks without text evidence are not treated as perfect, and coverage is reported separately.", - "接口正常返回的次数占全部调用的比例。接口返回成功,不代表抓到了目标内容。": "Share of calls for which the interface returned normally. A successful response does not mean the requested content was retrieved.", - "必须有完整参考,且与输出的目标范围和内容具有可比性;不能用条目数量推算全文 F1。请求失败计 0;请求成功但任务未通过,仍按实际正文评分,所以 0 次通过不等于 F1 为 0。逐案例计算后取平均;平均 F1 不是平均完整度与平均纯净度的调和平均。": "A complete reference comparable in scope and content is required; record counts cannot be used to infer full-text F1. Request failures score 0. A successful request that fails the task is still scored against its actual text, so 0 passes does not imply an F1 of 0. Metrics are calculated per task and then averaged; mean F1 is not the harmonic mean of mean recall and mean precision.", - "均值 · P50 · P95": "Mean · P50 · P95", - "仅统计该类型任务通过的调用耗时。列表任务与正文任务均按各自声明的规则判定。P50 是中位数;P95 表示约 95% 的通过调用不超过该耗时。": "Latency includes only passing calls in this task family. List and body-text tasks are judged by their own declared rules. P50 is the median; P95 is the latency not exceeded by approximately 95% of passing calls.", - "资源 · 文件 · 信息": "Resources · files · metadata", - "资源命中率:资源任务中找齐指定入口的比例;文件取得率:原文件任务中取得可验证字节的比例;信息通过率:信息任务中通过文字核对的比例。文件缺少字节证据会使报告不完整并禁止发布。": "Resource match rate is the share of resource tasks returning every required endpoint; file delivery rate is the share of original-file tasks yielding verifiable bytes; metadata pass rate is the share of metadata tasks passing their text checks. Missing byte evidence makes the report incomplete and blocks publication.", - "稳定任务契约": "Stable task contract", - "同时核对登记的稳定内容锚点(稳定标题、栏目、说明)与字段结构、数量、唯一性及类型,不是只检查数量。频繁变化的名单、条目标题和价格按声明的结构与类型核验,不要求与旧快照取值相同。通过不证明这些变化值的事实准确性或实时性。": "Validate registered stable content anchors (stable headings, sections, and descriptions) together with field shape, count, uniqueness, and types—not count alone. Frequently changing lists, item titles, and prices are checked against declared shape and types rather than old snapshot values. A pass does not establish the factual accuracy or freshness of those values.", - "举例": "Example", - "通过任务的平均耗时,单位毫秒。": "Mean latency of passing tasks, in milliseconds.", - "通过任务的中位耗时,单位毫秒。": "Median latency of passing tasks, in milliseconds.", - "通过耗时从小到大排序,取第 ⌈95% × 样本数⌉ 个值,单位毫秒。": "Passing latencies sorted ascending, using the item at ⌈95% × sample count⌉, in milliseconds.", - "逐案例计算完整度与纯净度的调和平均,再对案例 F1 取均值;没有完整参考不计算。0 次通过不等于 F1 为 0。": "Compute the harmonic mean of recall and precision per task, then average task F1. Tasks without a complete reference are excluded. 0 passes does not imply an F1 of 0.", - "本次调用从发起到结束的时间,包含失败调用,单位毫秒。": "Elapsed time from call start to completion, including failed calls, in milliseconds.", - "按执行记录及开始时间生成的稳定本地标识,重测可区分;供应商请求 ID 另见详情。": "Stable local identifier derived from the execution record and start time; reruns remain distinguishable. The provider request ID is shown separately when available.", - "识别出的不同条目数,每条需有名称和本站详情链接。": "Number of distinct identified records, each with a name and a same-site detail URL.", - "资源任务中,找齐所有指定图片或播放入口的比例。": "Share of resource tasks returning every required image or playback endpoint.", - "已测文件任务中有原文件验收通过记录的比例;历史缺少字节验收时显示 —,不是 0% 或产品不支持。": "Share of measured file tasks with an accepted original-file record. Historical calls without byte validation are shown as —, not 0% or lack of product support.", - "文字信息任务中,通过指定标题、发布者等核对的比例。": "Share of text-metadata tasks passing required checks such as title and publisher.", - - # Detailed task and attempt pages. - "这项分数怎样计算:": "How this score is calculated:", - "如何检查是否抓全": "How body coverage is checked", - "这是一份完整答案,不是 5 个任务。": "This is one complete reference, not 5 separate tasks.", - "内容核验": "Content validation", - "获取记录": "Retrieval record", - "直接返回依据": "Direct-response basis", - "独立源页复查": "Independent source-page check", - "复查边界": "Recheck boundary", - "独立复查与调用的时间、网络、会话不同;一次拒绝不能证明网站失效。": "The independent recheck used a different time, network path, and session. A single denial does not establish that the site was unavailable.", - "已独立核对任务目标;正文、资源地址或原文件的评分依据见本例记录。": "The task target was checked independently; this record shows the body text, resource URL, or original-file evidence used for scoring.", - "允许表达": "Accepted variants", - "列表任务:通过": "List task: pass", - "列表任务:未通过": "List task: fail", - "读取条目": "Parse records", - "对齐独立参考": "Join independent references", - "比较标题文字": "Compare title text", - "解释结果": "Interpret the result", - "逐条标题对照与来源": "Per-title comparison and source", - "独立参考": "Independent reference", - "技术校验值": "Technical checksums", - "参考 SHA-256:": "Reference SHA-256:", - "评分实现 SHA-256:": "Scorer SHA-256:", - "返回标题": "Returned title", - "参考标题": "Reference title", - "条目身份": "Record identity", - "没有可验证的原文件字节;文字或链接不能替代文件": "No verifiable original-file bytes; text or a URL cannot substitute for the file.", - "已返回原始文件,格式特征、长度及目标 SHA-256 校验通过。": "Original-file bytes were returned and passed format signature, length, and target SHA-256 validation.", - "目标文件": "Target file", - "本任务验收原始文件字节,不要求 Markdown。": "This task validates original-file bytes; Markdown is not required.", - "文件要求": "File requirements", - "目标 SHA-256": "Target SHA-256", - "本例按任务结构或原文件输出判定。": "This task is judged by its record structure or original-file output.", - "文本输出字符": "Text-output characters", - "文本输出校验值": "Text-output checksum", - "下载交付文件": "Download delivered file", - "字节数": "Bytes", - "目标校验": "Target check", - "匹配": "Match", - "识别格式": "Detected format", - "文件": "File", - "格式特征": "Format signature", - "实际输出存在解码损坏:": "The returned text contains decoding corruption:", - "请求失败": "Request failure", - "请求成功但任务失败": "Request succeeded; task failed", - "失败环节": "Failure stage", - "失败类别": "Failure category", - "有效条目不足": "Insufficient valid records", - "加载 / 提取超时": "Load / extraction timeout", - "未取得原文件": "Original file not obtained", - "返回非目标内容": "Wrong content returned", - "返回验证或拦截页面": "Verification or block page returned", - "服务取页": "Provider retrieval", - "服务未披露更细原因;不能仅凭该错误归责源站或服务内部": "The provider did not expose a more specific cause; this error alone cannot attribute fault to the origin or the provider internals.", - "正文核对点-1": "Body check 1", - "正文核对点-2": "Body check 2", - "正文核对点-3": "Body check 3", - "参考状态:完整正文。": "Reference status: complete body text.", - "无独立名称参考,未评分": "No independent name reference; not scored", - "本次没有可逐条对照的返回标题,因此不显示空表。": "No returned titles can be compared item by item, so an empty table is not shown.", - "本次没有交付可校验的原始文件字节。": "This call did not deliver verifiable original-file bytes.", - "本例未设置稳定文字核对点,不作文字内容已核验的结论。": "No stable text checks are registered for this task, so no claim is made that its text content was validated.", - "—(接口未提供)": "— (not provided by the interface)", - "—(未设置)": "— (not configured)", - "—(未保存源页快照)": "— (source-page snapshot not retained)", - "— / — / —(全文指标无完整参考,未评测)": "— / — / — (not measured; no complete full-text reference)", - "完整度 / 纯净度 / F1 为 — / — / —。": "Recall / precision / F1 are — / — / —.", - "快照文字诊断 · 完整度 / 纯净度 / F1": "Snapshot-text diagnostic · recall / precision / F1", - "列表任务依据;无完整文本参考": "List-task evidence; no complete text reference", - "快照文字诊断;含非固定字段": "Snapshot-text diagnostic; includes non-invariant fields", - "稳定内容+字段结构契约": "Stable content + field-structure contract", - "具名详情入口": "Named detail records", - "完整正文与词语重合评分的参考。": "Reference for complete body text and text-overlap scoring.", - "正文抽取的精确率、召回率与 F1。": "Precision, recall, and F1 for body-text extraction.", - "直接 PDF 与嵌入文档阅读器案例。": "Direct-PDF and embedded-document-viewer tasks.", - "图片和视频入口案例。": "Image- and video-endpoint tasks.", - - # Methodology and independent review. - "从参考到发布": "From reference capture to publication", - "评测流程": "Evaluation workflow", - "1 · 冻结参考": "1 · Freeze references", - "独立采集源页;需要渲染的页面另做浏览器观测,通过完整范围校验后才冻结。": "Capture source pages independently. Pages requiring rendering receive a separate browser observation and are frozen only after full-scope validation.", - "2 · 执行调用": "2 · Run provider calls", - "同一 URL、任务和截止时间;各方法使用其最快的正式任务原生产物路线,保留版本、输出与哈希。": "Use the same URL, task, and deadline. Each method uses its fastest official task-native output path, while version, output, and hashes are retained.", - "3 · 测后核验": "3 · Post-run validation", - "独立重取精确参考页,确认调用期间标准答案未改变;不把参考变化算成浏览器失败。": "Retrieve the exact reference page independently after the run to confirm that the answer did not change during measurement; reference drift is not counted as a browser failure.", - "4 · 结果独立审查": "4 · Independent result review", - "强代理逐次读取完整输出、标准答案与任务规则;分歧必须裁定,未裁定项会阻止构建。": "A strong agent reviews every complete output against the reference and task contract. Disagreements require adjudication; unresolved items block the build.", - "每类怎么测": "How each task family is evaluated", - "指标说明": "Metric definitions", - "共同输入、计时边界与调用预算": "Shared inputs, timing boundary, and call budget", - "计入耗时": "Included in latency", - "不计入耗时": "Excluded from latency", - "评分来源与修订": "Scoring provenance and revisions", - "稳定任务契约与全文诊断": "Stable task contracts and full-text diagnostics", - "方法图解": "Method diagrams", - "参考标注": "Reference annotation", - "任务验收": "Task acceptance", - "评分口径": "Scoring semantics", - "参考与匹配": "Reference normalization and matching", - "调用时间": "Call timing", - "变更后重跑范围": "Rerun scope after a change", - "计算与边界": "Calculations and limits", - "全文评分公式": "Full-text scoring formula", - "参考范围与公平性": "Reference scope and fairness", - "当前状态": "Current status", - "核验口径": "Review protocol", - "核验协议与执行者": "Review protocol and reviewer", - "执行者:": "Reviewer:", - "模型:": "Model:", - "推理强度:": "Reasoning effort:", - - # Reproduction, setup, and publishing. - "同窗速度证据": "Same-window latency evidence", - "方法版本": "Method versions", - "速度汇总与共同成功样本": "Latency summary and shared-pass subset", - "速度汇总与样本分母": "Latency summary and sample denominators", - "速度子集逐次记录": "Per-call latency-cohort records", - "唯一输出目录为 reports/webfetch/。固定的 Python 3.11 与 uv.lock 依赖不满足时会立即失败。重新测量使用 uv run --frozen python scripts/run_headless_benchmark.py 和 configs/providers.headless.json;先独立采集并冻结参考,再调用,最后做测后核验。完整命令与版本见仓库 harness/web_fetch/HEADLESS.md。": "The only output directory is reports/webfetch/. The build fails immediately if the pinned Python 3.11 and uv.lock environment is unavailable. Fresh measurement uses uv run --frozen python scripts/run_headless_benchmark.py and configs/providers.headless.json: capture and freeze references independently, run provider calls, then perform post-run validation. See harness/web_fetch/HEADLESS.md for complete commands and versions.", - "Moli 1.0.6 正式发布": "Moli 1.0.6 release", - "Moli 1.1.3 正式发布": "Moli 1.1.3 release", - "从官方 v1.0.6 release 获取二进制,配置 MOLI_1_0_6_BINARY;发布归档与可执行文件均校验 SHA-256。": "Obtain the binary from the official v1.0.6 release and configure MOLI_1_0_6_BINARY; both the release archive and executable are verified by SHA-256.", - "从官方 v1.1.3 release 获取二进制,配置 MOLI_1_1_3_BINARY;发布包与可执行文件均校验 SHA-256。": "Obtain the binary from the official v1.1.3 release and configure MOLI_1_1_3_BINARY; both the release package and executable are verified by SHA-256.", - "发布门禁会扫描 HTML、JSON、下载文件和逐次输出中的常见 Token、私钥、Authorization 值与用户目录路径;第三方原始 HTML 中命中密钥形态的片段单独脱敏,计分输出保持逐字节不变。扫描结果和脱敏文件哈希随发布包保存。": "The publication gate scans HTML, JSON, downloads, and per-call outputs for common token forms, private keys, Authorization values, and user-directory paths. Secret-shaped fragments in third-party raw HTML are redacted separately while scored output remains byte-for-byte unchanged. Scan results and redacted-file hashes are retained with the release bundle.", - - # Method catalog, pricing, interfaces, and adoption. - "参评方法": "Evaluated methods", - "远程正文接口": "Remote body-text interface", - "本地原生产物": "Local native output", - "本地 DOM 转换": "Local DOM conversion", - "Kitesurf 能否本地部署": "Can Kitesurf be self-hosted?", - "当前结论": "Current conclusion", - "后续意向": "Stated future direction", - "当前官方接入说明": "Current official integration guide", - "官方开源计划说明": "Official open-source plan", - "能力、价格与接口": "Capabilities, pricing, and interfaces", - "官方能力": "Official capabilities", - "价格与计费": "Pricing & billing", - "采用统计": "Adoption sample", - "方法详情": "Method details", - "接口与示例": "Interfaces & examples", - "统计口径": "Counting methodology", - "检索样本": "Search sample", - "计数规则": "Counting rule", - "不能推出什么": "What the data cannot establish", - "方法归属规则": "Method-attribution rule", - "完整检索证据": "Complete search evidence", - "逐方法统计数据": "Per-method counts", - "命中仓库数": "Matched repositories", - "命中文件数": "Matched files", - "仓库": "Repository", - "命中文件": "Matched file", - "形态": "Form", - "做什么": "What it does", - "本轮输出": "Output in this benchmark", - "本轮新鲜度设置": "Freshness setting", - "账号与获取入口": "Account and official access", - "官方说明核验:2026-09-07。只列账号要求、获取入口与占位符,不公开真实凭据。": "Official documentation checked on 2026-09-07. Only account requirements, access links, and placeholders are shown; real credentials are never published.", - "本轮输出节选 · 统一记录": "Saved output excerpt · normalized record", - "核对实现:": "Implementation checked:", - "本轮执行模式的成本边界": "Cost boundary for the measured configuration", - "这是客户端实测,不是计费或同机 CPU / 内存测试;各方法的资料核验日期见表内说明。": "This is a client-observed benchmark, not a billing study or same-machine CPU/memory test; source verification dates are stated in the table notes.", - "获取 1,000 个网页,究竟付哪一种费用?": "Which charge applies to 1,000 page fetches?", - "如何读这个价格": "How to read these prices", - "各提供方计费方案": "Provider billing plans", - "展开查看原价、年付、追加用量、功能加价与失败扣费规则。": "Expand a provider to inspect list price, annual terms, overage, feature surcharges, and failed-request billing.", - "产品 / 模式": "Product / mode", - "原始计价": "Published pricing basis", - "每千页参考价": "Reference price per thousand pages", - "付费门槛": "Minimum commitment", - "优惠与限制": "Discounts and limits", - "价格状态": "Pricing status", - "成本边界": "Cost boundary", - "官方来源": "Official source", - "套餐": "Plan", - "每月积分": "Monthly credits", - "月付原价": "Monthly list price", - "年付总额与优惠": "Annual total and discount", - "满额每千页(月付 / 年付)": "Fully utilized price per thousand pages (monthly / annual)", - "额度外用量": "Overage", - "时长敏感性示例": "Duration sensitivity example", - "假设计费时长 / 页": "Assumed billable time / page", - "每千页边际价": "Marginal price per thousand pages", - "1,000 页/月总支出": "Total monthly spend at 1,000 pages", - "100,000 页/月总支出": "Total monthly spend at 100,000 pages", - "其他参评方法的成本归属": "Cost ownership for other evaluated methods", - - # Release-note UI. Release bodies are formal report copy, not evidence. - "查看修正依据、影响与边界": "View rationale, impact, and limits", - "发现的问题": "Issue found", - "正式修正": "Correction", - "结果影响与边界": "Result impact and limits", - "每个版本只记录影响读者判断的正式修正;结论与分数影响直接展示,技术证据按需展开。": "Each release records only formal corrections that affect interpretation. Conclusions and score effects are shown directly; technical evidence is available on demand.", - "查看文件任务逐方法结果": "View file-task results by method", - "计划并判定": "Planned and judged", - "查看原始运行与选择证据": "View original runs and selection evidence", - "选定运行清单": "Selected-run manifest", - "历史文件记录": "Historical file records", - "首轮调用": "Initial calls", - "原生文件适配复测": "Native-file adapter rerun", - "Crawl4AI 原生路径验证": "Crawl4AI native-path validation", - "Crawl4AI 最终调用": "Crawl4AI final calls", - "查看独立对照记录": "View independent control records", - "下载调用与哈希记录": "Download call and hash records", - "下载结构化版本记录": "Download structured release history", - "下载完整性状态": "Download integrity status", - "查看仓库变更": "View repository changes", - - # Native routes and interface descriptions used by measured methods. - "处理路线:Moli CLI 直接返回 Markdown": "Retrieval path: Markdown returned directly by the Moli CLI", - "处理路线:Lightpanda CLI 直接返回 Markdown": "Retrieval path: Markdown returned directly by the Lightpanda CLI", - "处理路线:Obscura CLI 直接返回 Markdown": "Retrieval path: Markdown returned directly by the Obscura CLI", - "处理路线:Cloudflare Browser Run /markdown 直接返回 Markdown": "Retrieval path: Markdown returned directly by Cloudflare Browser Run /markdown", - "处理路线:Chromium 渲染 DOM → node-html-markdown 2.0.0": "Retrieval path: Chromium rendered DOM → node-html-markdown 2.0.0", - "处理路线:方法直接取得原始文件 → 格式、长度与 SHA-256 验收": "Retrieval path: method obtains original file → format, length, and SHA-256 validation", - "处理路线:方法直接取得原始文档 → pypdf → 可读正文": "Retrieval path: method obtains original document → pypdf → readable body text", - "处理路线:参评方法直接返回正文": "Retrieval path: evaluated method returns body text directly", - "下载方法直接取得的原始文档": "Download the original document obtained by the method", - "本地可重复测试实现。": "Locally reproducible test implementation.", - "Markdown/资源地址;文件题为 rawBase64 解码字节": "Markdown/resource addresses; decoded rawBase64 bytes for original-file tasks", - "不点击、不滚动、不补抓资源,不在失败后切换引擎或输出模式。CLI 未提供可靠 HTTP 状态和最终 URL 时保持空值。": "No clicks, scrolling, or supplemental resource fetches, and no engine or output-mode switch after failure. HTTP status and final URL remain blank when the CLI does not provide reliable values.", - "Chromium/CDP 没有原生 Markdown 输出,因此取完整渲染 DOM 后转换;转换计入端到端耗时,HTML 与转换器版本、哈希均保留。": "Chromium/CDP has no native Markdown output, so the complete rendered DOM is converted. Conversion is included in end-to-end latency, and the HTML plus converter version and hashes are retained.", - - # Repeated task-contract and scoring prose. These are benchmark rules, - # not the task-specific reference text displayed in
 blocks.
-    "读取给定网页当前首屏加载完成后的整页正文;保留标题、正文、列表和表格,不要求翻页或点击展开。项目仓库页的正文目标为完整项目说明(README),不要求侧栏统计。": "Read the complete body visible after the target page's initial load. Preserve headings, prose, lists, and tables; pagination and click-to-expand content are out of scope. For repository pages, the complete project README is the body target; sidebar statistics are not required.",
-    "读取文档全部正文,包括后续页面、表格和附录;若输入为文档查看页,读取里面嵌入的文档,而非只读工具栏或外围介绍。": "Read the document's complete body, including later pages, tables, and appendices. When the input is a document viewer, read the embedded document rather than only its toolbar or surrounding description.",
-    "返回页面中指定正文图片的可用地址;至少必须命中全部目标。额外页面图片计入纯净度诊断,不改变二元通过判定;不把文字描述或输入网页地址当作目标图片。这里测试取图入口,不测试看图理解。": "Return usable URLs for every specified body image. All targets are required. Additional page images affect the purity diagnostic but not binary acceptance; textual descriptions and the input page URL are not target images. This evaluates image retrieval, not image understanding.",
-    "返回该详情页或示例结果中的指定正文图片地址;至少必须命中目标。Logo、广告和推荐作品等额外候选计入纯净度诊断,不改变二元通过判定。不测试图片内容理解。": "Return the specified body-image URL from the detail page or example output. The target is required. Extra candidates such as logos, ads, and recommendations affect the purity diagnostic but not binary acceptance. Image understanding is not evaluated.",
-    "返回页面里视频的播放文件或有效播放器入口。视频封面、页面标题和普通首页链接不算;此项不测试听写或理解视频。": "Return the playable video file or a valid player endpoint from the page. A poster image, page title, or ordinary home-page link does not count. Transcription and video understanding are out of scope.",
-    "返回可核对的原始视频文件;仅返回输入地址或二进制乱码不算成功。": "Return a verifiable original video file. Repeating the input URL or returning binary data as garbled text does not count.",
-    "返回原始 PNG 图片文件,不接受把图片乱码当作正文,也不接受只重复输入地址。": "Return the original PNG file. Binary bytes rendered as garbled text and a repeated input URL are not accepted.",
-    "已找到目标资源。": "The required resource was found.",
-    "打开任务源页 ↗": "Open task source ↗",
-    "详情 ↗": "Details ↗",
-    "先按失败环节区分请求失败、请求成功但任务失败,再按失败类别细分原因。“访问被拒绝”不等于网站失效。": "First separate request failures from successful requests that fail task acceptance, then classify the specific cause. “Access denied” does not mean the site was unavailable.",
-    "只检查标题和几个关键词,可能让“只抓到开头”的残缺结果通过。因此系统按前后位置检查同一份标准答案。每段硬下限 60.0%;至少 4/5 段达到 80.0%。 比对忽略空白、排版符号和 Markdown 链接地址;每次执行页会并列显示各位置的标准原文和实际证据。": "Checking only a heading and a few keywords can let an opening-only fragment pass. The benchmark therefore checks the same complete reference across ordered regions. Every region has a 60.0% hard floor, and at least 4 of 5 regions must reach 80.0%. Matching ignores whitespace, presentation punctuation, and Markdown link destinations; each attempt page shows the reference region beside the observed evidence.",
-    "评分使用上方完整标准答案和完整实际返回,不使用下面展示的短定位样例。两边先统一大小写和常见排版差异,去掉 Markdown 链接地址、空白及标点,再切成连续六字符片段。标准答案按前后位置分成 5 份;每份覆盖率 = 在完整实际返回中找到的片段数 ÷ 该份标准片段总数。本任务要求每个位置至少达到 60.0%,且至少 4/5 个位置达到 80.0%。同一个实际片段只能计入一个位置,避免重复文字被多次借用。全文完整度、纯净度和 F1 仍按完整文本另行计算。": "Scoring uses the complete reference and complete actual output above, not the short inspection snippets below. Both sides are normalized for case and common formatting differences; Markdown link destinations, whitespace, and punctuation are removed before forming contiguous six-character shingles. The reference is divided into 5 ordered regions. Region coverage equals matched shingles in the complete output divided by reference shingles in that region. Every region must reach the 60.0% hard floor and at least 4 of 5 regions must reach the 80.0% target. An output shingle can count toward only one region, preventing repeated text from being reused. Full-text recall, precision, and F1 are calculated separately on the complete text.",
-    "评分使用上方完整标准答案和完整实际返回,不使用下面展示的短定位样例。两边先统一大小写和常见排版差异,去掉 Markdown 链接地址、空白及标点,再切成连续六字符片段。标准答案按前后位置分成 1 份;每份覆盖率 = 在完整实际返回中找到的片段数 ÷ 该份标准片段总数。本任务要求每个位置至少达到 60.0%,且至少 1/1 个位置达到 80.0%。同一个实际片段只能计入一个位置,避免重复文字被多次借用。全文完整度、纯净度和 F1 仍按完整文本另行计算。": "Scoring uses the complete reference and complete actual output above, not the short inspection snippets below. Both sides are normalized for case and common formatting differences; Markdown link destinations, whitespace, and punctuation are removed before forming contiguous six-character shingles. This reference has 1 region. Region coverage equals matched shingles in the complete output divided by reference shingles in that region. At least 1 of 1 region must reach the 60.0% hard floor and the 80.0% target. An output shingle can count only once. Full-text recall, precision, and F1 are calculated separately on the complete text.",
-    "每组至少命中一种允许表达,所有组均须通过。": "At least one accepted variant must match in every group; all groups are required.",
-    "这些文字只用于核对抓取范围;未命中不会使任务失败。": "These texts only verify retrieval scope; missing them does not fail the task.",
-    "范围核对文字": "Scope-check text",
-    "这里列出正文验收规则和用于范围核对的文字;范围核对文字不影响是否通过。": "This section lists the body acceptance rules and text used to check retrieval scope; scope-check text does not affect acceptance.",
-    "必需字段与要求": "Required fields and constraints",
-    "名称与链接核验依据": "Name and URL validation basis",
-    "各字段须属于同一条记录,不得跨条目拼接。按详情链接核对唯一性。": "All fields must belong to the same record and cannot be assembled across items. Uniqueness is checked by detail URL.",
-    "具名详情入口:至少 3 条完整且唯一的记录。按登记的稳定文本锚点与页面模式核对稳定标题、栏目、说明,同时检查条目字段归属、类型、完整性和唯一性。频繁变化的名单和价格不固定旧快照取值;通过不证明这些变化值真实或最新。": "Named detail records: at least 3 complete, unique records. Registered stable text anchors and page patterns validate stable headings, sections, and descriptions, together with field ownership, type, completeness, and uniqueness. Frequently changing lists and prices are not compared with old snapshot values; a pass does not establish that those values are accurate or current.",
-    "动态网站正文已测方法:chromium、kitesurf、lightpanda、moli、moli-1.0.6、moli-1.1.3、obscura。未测方法不计为失败。": "Methods measured on rendered-web tasks: chromium, kitesurf, lightpanda, moli, moli-1.0.6, moli-1.1.3, and obscura. Unmeasured methods are not counted as failures.",
-    "超过该调用登记的端到端时间预算;保留实际耗时和返回内容,但不计为按时交付。": "The call exceeded its registered end-to-end deadline. Actual latency and returned content are retained, but the result does not count as on-time delivery.",
-    "标题完整度 / 标题纯净度 / 标题 F1": "Title recall / title precision / title F1",
-    "标题参考覆盖": "Title reference coverage",
-    "参考状态:资源清单。": "Reference status: resource manifest.",
-    "允许地址": "Accepted URLs",
-    "独立文字依据:": "Independent text evidence:",
-    "查看限定范围原文": "View source text for the declared scope",
-    "稳定文本锚点": "Stable text anchors",
-    "资源核验": "Resource validation",
-    "调用截止时间": "Call deadline",
-    "支持 · 兼容范围看逐例结果": "Supported · see per-task results for compatibility",
-    "该次完整输出与判定 →": "Complete output and verdict →",
-
-    # Task-family narratives shown on the overview and methodology pages.
-    "常见网页的正文提取:从文章、文档站、百科或项目主页中取回要读的内容,剔除导航、广告等干扰。难点是找对正文边界,既不漏段落,也不混入大量杂文。": "Extract readable body text from articles, documentation sites, encyclopedias, and project pages while excluding navigation, advertising, and other chrome. The challenge is finding the correct body boundary without omitting sections or admitting substantial noise.",
-    "请求网页": "Request page",
-    "定位正文区域": "Locate body region",
-    "去除导航等杂文": "Remove navigation and noise",
-    "按题目范围保留文字": "Retain text within the task scope",
-    "从文章、文档站、百科或项目主页中取回题目要求的内容。导航和页脚是否需要保留,由每道题登记的范围决定;它们不能替代要求的正文、列表或表格。": "Retrieve the required content from articles, documentation, encyclopedias or project pages. Each task declares whether navigation and footer text must be retained; they cannot substitute for the required body, lists or tables.",
-    "输出正文": "Return body text",
-    "目标是读出页面内容,而不是保存整份网页源码。正文若已在响应中,普通 HTTP 加解析器就可能完成。": "The goal is readable page content, not a copy of the complete page source. When the body is already present in the response, ordinary HTTP plus a parser may be sufficient.",
-    "项目仓库页以 README 为正文:只返回仓库名称和简介不够;把文件目录、导航一起带回来,也可能降低纯净度。": "For a repository page, the README is the body target. A repository name and short description are insufficient, while including the file tree and navigation may reduce precision.",
-    "这组按正文提取任务选样,没有逐页按渲染方式分组,不能等同于“纯静态网页”。请求成功、任务通过、全文质量分别衡量;不测站内搜索、点击或多页浏览。": "This family is sampled for body-text extraction and is not subdivided by rendering mechanism, so it should not be read as “purely static pages.” Request success, task acceptance, and full-text quality are measured separately. Site search, clicking, and multi-page browsing are out of scope.",
-    "这里的“动态”指内容的加载方式,不是文字经常更新。目标正文不直接以完整可读文本出现在初始页面中,还需异步取数、客户端组装、等待呈现或加载嵌入子页。难点是从页面壳找到真正的数据。": "Here, “rendered” describes how content loads, not how often it changes. The complete readable target is absent from the initial page and requires asynchronous data, client-side assembly, delayed presentation, or an embedded child page. The challenge is finding the actual data behind the shell.",
-    "取得页面外壳": "Receive page shell",
-    "浏览器运行页面": "Run page",
-    "加载数据或嵌入子页": "Load data or embedded page",
-    "目标内容就绪": "Wait for target content",
-    "提取正文与字段": "Extract body and fields",
-    "这是常见的浏览器渲染路径,可由 Web Fetch 内部完成,无需先手动打开网页。只取初始 HTML 可能缺正文;但若能直接解析内嵌数据、请求公开接口或读取子页,也不必走完浏览器渲染流程。": "This is a typical browser-rendering path and may be performed internally by Web Fetch without manually opening the page first. Initial HTML alone may omit the body, but directly parsing embedded state, calling a public endpoint, or reading the child page are equally valid paths.",
-    "Quotes 异步引文页:只拿到页面标题不够,要取得首批引文及每条对应的作者、标签;不能把不同条目的字段拼在一起。": "On the asynchronous Quotes page, the page title alone is insufficient. The first set of quotes and each quote's corresponding author and tags are required; fields cannot be combined across records.",
-    "只测各案例声明的初次自动加载范围,不要求滚动、翻页或点击。执行 JavaScript 不是唯一合格路线,解析内嵌数据或调用公开接口也可。固定演示文字仍核对内容,易变字段不固定旧值。": "Only each task's declared initial automatic-load scope is measured; scrolling, pagination, and clicks are not required. Running JavaScript is not the only valid approach—embedded data and public endpoints are acceptable. Stable demonstration text is still checked, while volatile fields are not frozen to old values.",
-    "从有风控、登录限制或复杂内容组织的热门网站提取任务正文:既要取得列表,也要保留登记的栏目、说明或固定字段标签。只返回页面壳、登录提示或几个孤立链接,不足以满足有正文核对项的任务。": "Retrieve task-relevant content from popular sites with anti-abuse controls, login restrictions, or complex information architecture. The result must include the list together with registered sections, descriptions, or stable field labels. A page shell, login prompt, or a few isolated links is insufficient when body checks are defined.",
-    "请求目标网站": "Request target site",
-    "识别内容页或受限页": "Identify content or restriction page",
-    "提取稳定说明与栏目": "Extract stable descriptions and sections",
-    "提取列表与详情链接": "Extract records and detail URLs",
-    "接口正常返回也可能只是登录页或验证页。只有到达目标内容,并满足本例条目要求,才算任务通过。": "A normal interface response may still contain only a login or verification page. The task passes only when it reaches the target content and satisfies the record requirements.",
-    "IMDb:取得至少三部不同影片的名称与详情链接,还须保留榜单标题和“由常规用户投票”的说明;影片名称、名次与评分不固定为旧值。": "IMDb: return names and detail URLs for at least three distinct films, plus the chart heading and its description that rankings are based on regular-user votes. Film names, ranks, and ratings are not frozen to old values.",
-    "不向方法提供站点登录凭据,不操作验证码;某一次访问受限不等于网站失效。标题完整度、纯净度和 F1 只衡量返回条目与独立记录名称的文字重合,不测整页正文、榜单召回、实时排名或价格。标题本身也可能被编辑,文字差异不直接改判任务结果。": "Methods receive no site login credentials and do not operate CAPTCHAs; one restricted request does not mean the site is down. Title recall, precision, and F1 measure only textual overlap between returned records and independently recorded names. They do not measure whole-page text, list recall, current ranking, or prices. Titles may also be edited, so a text difference does not directly change task acceptance.",
-    "该诊断回答“已经返回的条目标题是否完整、干净”,不衡量榜单召回率、实时性或整页正文质量。": "This diagnostic asks whether titles that were returned are complete and clean. It does not measure list recall, freshness, or whole-page body quality.",
-    "从结构化结果中读取标题与详情链接,共 952 个标题。": "Read titles and detail URLs from structured results: 952 returned titles.",
-    "571/952 个标题找到独立名称证据;没有对应证据的不评分。": "571/952 titles join to independent name evidence; titles without matching evidence are not scored.",
-    "分别计算标题完整度、纯净度和 F1,格式符号不当作标题内容。": "Calculate title recall, precision, and F1 separately; formatting marks are not title content.",
-    "覆盖 6/10 个案例;这是标题诊断,不参与任务通过判定。": "Coverage: 6/10 tasks. This is a title diagnostic and does not determine task acceptance.",
-    "读取 PDF、DOCX、TXT 以及网页内嵌文档中的文字。与普通网页不同,正文可能在文件或查看器后面;难点是识别文档并保留跨页、段落和表格中的内容。": "Read text from PDF, DOCX, TXT, and documents embedded in web pages. Unlike ordinary web pages, the body may sit behind a file or viewer. The challenge is identifying the document and retaining content across pages, paragraphs, and tables.",
-    "打开文件或查看页": "Open file or viewer",
-    "定位实际文档": "Locate the actual document",
-    "解析页面、段落与表格": "Parse pages, paragraphs, and tables",
-    "输出文档文字": "Return document text",
-    "下载链接只是入口;正文提取还需读取文件内容。内嵌查看器的菜单、按钮不是文档正文。": "A download link is only an entry point; body extraction still requires reading the file. Menus and buttons in an embedded viewer are not document content.",
-    "Attention Is All You Need PDF:需要论文正文并命中标题、Transformer 等核对点;只返回下载地址不通过,只提取首页会丢失后续全文。": "Attention Is All You Need PDF: the paper body must be returned and checks such as the title and Transformer must match. A download URL alone fails, and extracting only the first page omits later content.",
-    "衡量文字提取,不衡量原版排版还原、图表理解,也不能据此推断扫描件 OCR 能力。拿到文件与读出文件内容是两件事,全文质量仍单独计算。": "This family measures text extraction, not fidelity to the original layout or understanding of charts, and it does not establish OCR capability on scanned documents. Obtaining a file and reading its contents are distinct outcomes; full-text quality is calculated separately.",
-    "定位网页中的目标图片,或取得直接图片文件。难点是区分正文图片、缩略图和装饰图,并保留可用的资源地址;这不是让方法描述图片里画了什么。": "Locate target images in a web page or obtain a directly addressed image file. The challenge is distinguishing body images from thumbnails and decoration while retaining usable resource URLs; describing image content is out of scope.",
-    "读取含图页面": "Read image-bearing page",
-    "定位指定正文图片": "Locate specified body image",
-    "解析实际资源地址": "Resolve actual resource URL",
-    "返回图片链接": "Return image URL",
-    "这是含图网页的任务路径。直接图片 URL 属于另一类文件任务:请求图片 → 返回原始字节 → 校验文件,不能用链接代替文件。": "This is the path for an image-bearing page. A direct image URL is a separate file task: request image → return original bytes → validate file. A URL cannot substitute for the file.",
-    "含图页面须在实际返回值中找齐指定图片地址;直接 PNG 须交付文件字节并通过格式、长度与目标 SHA-256 验收。": "Image-bearing pages must return every specified image URL. A direct PNG task must deliver file bytes that pass format, length, and target SHA-256 validation.",
-    "MDN 响应式图片页:返回参考清单中的两张图片地址。文字写着“这里有图片”不算命中;直接 PNG 任务则不能仅复述输入网址。": "MDN responsive-images page: return both image URLs in the reference manifest. Text stating that an image exists is not a match; a direct-PNG task cannot merely repeat the input URL.",
-    "资源命中只证明返回了指定地址,不证明图片字节已下载或内容已理解。历史原文件记录缺少字节验收证据,不能用于断言某产品不支持下载。": "A resource match proves only that the specified URL was returned, not that image bytes were downloaded or understood. Historical file records without byte-validation evidence cannot support a claim that a product lacks download support.",
-    "分别测试网页播放入口、直接视频文件,以及视频标题和发布者。难点是辨别播放器外壳、实际媒体地址与文字信息;这三类输出不能互相替代。": "Separately test web playback endpoints, direct video files, and video title/publisher metadata. The challenge is distinguishing the player shell, actual media URL, and textual metadata; these deliverables cannot substitute for one another.",
-    "读取视频页面": "Read video page",
-    "定位播放器或信息区": "Locate player or metadata region",
-    "解析媒体入口或文字": "Resolve media endpoint or text",
-    "交付本例指定结果": "Deliver the task-specific result",
-    "按题目返回播放地址,或标题与发布者,不要求每题同时完成所有步骤。直接视频 URL 另走文件下载与字节校验路径。": "Return the playback URL or title and publisher as required by the task; not every task requires every step. A direct video URL follows the separate file-download and byte-validation path.",
-    "按案例区分:找齐播放入口、核对标题与发布者,或交付通过格式、长度与目标 SHA-256 验收的原文件。链接不等于文件,均不测画面理解。": "Depending on the task, return every playback endpoint, validate title and publisher, or deliver an original file that passes format, length, and target SHA-256 checks. A URL is not a file, and none of these tasks measure visual understanding.",
-    "YouTube 的 Me at the zoo 信息任务核对标题与发布者 jawed;MDN 视频页另测播放地址。前者通过,不代表已经下载或看懂了视频。": "The YouTube Me at the zoo metadata task checks its title and publisher, jawed; the MDN video-page task separately checks playback URLs. Passing the former does not mean the video was downloaded or understood.",
-    "不测画面理解、语音转写或字幕质量。找到播放入口不等于取得视频字节;历史调用缺少原文件字节验收,相关记录显示未测,不计为产品失败。": "Visual understanding, speech transcription, and subtitle quality are out of scope. Finding a playback endpoint is not the same as obtaining video bytes. Historical calls without original-file byte validation are shown as unmeasured rather than product failures.",
-
-    # Interpretation page.
-    "同一 URL、同一任务交付物、同一评测集与验收规则;每种方法使用本轮可获得的最快正式路径,不强迫内部实现一致。": "Every method receives the same URL, required deliverable, dataset, and acceptance rules. Each uses the fastest official path available in this benchmark; internal implementations are not forced to match.",
-    "先看任务是否交付": "Start with task delivery",
-    "任务通过数 / 实际调用数。六类任务不合成总分;未执行不记为失败。": "Task passes divided by actual calls. The six task families are not blended into a weighted score, and unexecuted tasks are not counted as failures.",
-    "浏览器": "Web text",
-    "文档文字": "Document text",
-    "图片": "Images",
-    "视频": "Video",
-    "Moli 版本结论:": "Moli version finding:",
-    "方法各走最快的正式交付路径": "Each method uses its fastest official delivery path",
-    "版本未记录不是可忽略信息;二进制哈希仍保留在环境与复现页。": "A missing version is material information; binary hashes remain available on the reproduction page.",
-    "部署": "Deployment",
-    "完成条件": "Completion condition",
-    "实际路径": "Observed path",
-    "本次请求": "This call",
-    "原生 Markdown ms": "Native Markdown (ms)",
-    "取 HTML ms": "HTML retrieval (ms)",
-    "转换 ms": "Conversion (ms)",
-    "服务端浏览器 ms": "Server browser (ms)",
-    "端到端 ms": "End to end (ms)",
-    "公平边界在输入、输出和裁判,不在内部形态": "Fairness is aligned at inputs, deliverables, and judging—not internal architecture",
-    "远端方法包含公网往返、排队与远端执行;本地方法包含进程启动和本机资源竞争。不同批次的耗时不直接排名;同窗队列保留全部预声明任务,并把相同分母的全调用耗时与各方法成功子集的条件耗时分开。它衡量客户端形成可评分交付物的时间,不是纯内核 CPU、长期吞吐或 SLA。": "Remote methods include public-network round trips, queueing, and remote execution; local methods include process startup and local resource contention. Latencies from different batches are not ranked directly. The same-window cohort retains every predeclared task and separates all-call latency on a common denominator from latency conditioned on each method's successful subset. It measures the time until the client forms a judgeable deliverable, not engine-only CPU, sustained throughput, or an SLA.",
-    "可能偏差": "Potential bias",
-    "引入位置": "Where it enters",
-    "本报告处理": "Benchmark treatment",
-    "原生 Markdown 与二次转换": "Native Markdown vs. secondary conversion",
-    "输出形成阶段": "Output-production stage",
-    "允许最快正式路径;统一最终 Markdown 与裁判": "Allow the fastest official path; align the final Markdown deliverable and judge",
-    "远端网络、排队与本地资源": "Remote network/queueing vs. local resources",
-    "调用与执行阶段": "Call and execution stage",
-    "全部计入端到端时间;仅同窗共同成功样本排名": "Include all in end-to-end latency; rank only shared passes in the same window",
-    "全部计入端到端时间;同窗完整样本与条件成功样本分列": "Include all in end-to-end latency; separate the complete same-window sample from the conditional successful sample",
-    "混合标注:稳定文字、易变字段与全文参考": "Mixed annotation: stable text, volatile fields, and full-text references",
-    "页面中稳定标题、栏目与说明作为内容核对点;变化条目检查数量、字段类型和结构;全文参考单独声明范围。": "Stable headings, sections, and explanations are content checks; volatile records are checked for count, field types, and structure; full-text reference scope is declared separately.",
-    "稳定文字仍做内容核对,易变值不固定为答案;参考文本的范围决定质量指标能解释什么。": "Stable text remains part of content validation, while volatile values are not frozen as answers; reference scope determines what the quality metrics can explain.",
-    "校验:独立来源、正反样例与原始输出": "Validation: independent sources, positive and negative examples, and raw output",
-    "独立来源支撑任务标注,完整字段与空占位作为结构校验的正反样例;保存原始输出及其哈希供复核。": "Independent sources support task annotations; complete fields and empty placeholders provide positive and negative structural examples; raw outputs and their hashes are retained for review.",
-    "来源独立、规则有正反样例、输出可按哈希追溯,是不同的证据要求;哈希本身不证明内容正确。": "Source independence, positive and negative rule examples, and hash-traceable output are distinct evidence requirements; a hash alone does not establish correctness.",
-    "计分:任务验收与文本重合分别计算": "Scoring: task acceptance and text overlap are calculated separately",
-    "内容与结构检查产生任务通过或失败;有明确范围的参考另算纯净度、完整度与 F1。零通过仍可能有词语重合。": "Content and structure checks produce the task verdict; a clearly scoped reference separately yields precision, recall, and F1. Zero passes can still have textual overlap.",
-    "任务验收与全文 P/R/F1 回答不同问题。未通过不抹去已测得的词语重合,缺少参考也不把 F1 记为零。": "Task acceptance and full-text P/R/F1 answer different questions. Failure does not erase measured textual overlap, and a missing reference does not turn F1 into zero.",
-    "点击图像获取原图。": "Select the image to download the original.",
-    "同一时间窗的速度怎样读": "How to read the same-window latency cohort",
-    "主结果来自不同批次,耗时只用于逐次诊断;下面单列预先声明的同窗子集。全调用统计保留失败与截止超时,回答完整任务队列要等多久;通过调用统计只描述各方法已通过任务,成功分母可能不同,不能单独拿来排同题速度。": "Primary results come from different batches, so their latency is only a per-call diagnostic. The separately reported cohort below was declared before measurement in one time window. All-call statistics retain failures and deadline timeouts and answer how long the complete task queue takes. Successful-call statistics describe only tasks passed by each method; their denominators may differ and cannot independently rank matched-task speed.",
-    "测速逐次记录": "Per-call latency records",
-    "保留可复核证据。": "retain reviewable evidence.",
-    "逐例审计摘要与校验值": "Per-task audit summary and checksums",
-    "保留了核查依据。": "retain the audit basis.",
-    "这些互补案例暴露了不同缺口。Quotes to Scrape 的": "These complementary tasks expose different gaps. The Quotes to Scrape",
-    "页面在 CowAgent 输出里留下页面壳,名言和作者核对点缺失;飞书帮助文档只有标题,没有要求的正文。零售退换货页则是请求被拒绝。前两例要靠内容判断才能触发后备方法,最后一例可以由请求错误触发。因此,只在网络异常时切换方法,无法复现这里看到的全部互补覆盖。": "page leaves only a page shell in CowAgent output, with quote and author checks missing; the Feishu help document contains only its title, not the required body. The retail returns page is rejected at request time. The first two need content validation to trigger a fallback, while the last can use a request error. Switching methods only on network errors therefore cannot reproduce all of the complementary coverage observed here.",
-    "页面壳也不能直接证明正文必须经异步请求取得。对初始源文档的复核发现,": "A page shell also does not prove that the body must be fetched asynchronously. Review of the initial source documents found that",
-    "的名言载荷和飞书文章内容已经嵌入 HTML;取回源字节与正确提取嵌入内容是两件事。它们揭示的是当前提取路径的缺口,不能作为浏览器执行能力的独立证据。": "already embeds both the quote payload and the Feishu article content in HTML. Retrieving source bytes and correctly extracting embedded content are distinct tasks. These cases expose a gap in the current extraction path; they are not independent evidence that browser execution is required.",
-    "十二方法机器失败项盲审与分歧裁决": "Blind review and adjudication of machine failures across twelve methods",
-    "失败项盲审发现 2 条机器判定与独立审查分歧,均为 Exa 正文名称与另一组裸 URL 被审查者按顺序推断成具名链接。": "Failure-only blind review found 2 disagreements with machine verdicts. In both, the reviewer inferred named Exa links by positionally joining body names to a separate array of bare URLs.",
-    "两条分歧分别由独立代理回到任务合同、完整返回和原生字段裁决;由于输出没有明确的名称—URL 配对,均维持机器失败。审查记录保存模型、协议、证据绑定与裁决理由。": "Independent agents adjudicated both disagreements against the task contract, complete response, and native fields. Because neither output explicitly paired names with URLs, both machine failures were retained. The review record preserves the model, protocol, evidence bindings, and adjudication reasons.",
-    "Web Fetch · 实测分析": "Web Fetch · Measured analysis",
-    "网页抓取成功之后,内容离可用还有多远": "After a web fetch succeeds, how far is the content from usable?",
-    "正常返回,是内容验收的起点": "A normal response is only the start of content acceptance",
-    "通过更多网页的方法,未必交付更干净的正文": "The method that passes more pages may not deliver cleaner body text",
-    "固定内容与变化列表,需要不同的验收问题": "Fixed content and changing lists need different acceptance questions",
-    "普通文本提取能覆盖哪些任务": "What can plain-text extraction cover?",
-    "比再排一次名更有用的,是比较失败是否重叠": "Failure overlap is more useful than another ranking",
-    "换成文件或媒体,交付边界先于方法排名": "For files and media, define the deliverable before ranking methods",
-    "热门网站正文:内容可以变化,交付契约仍然稳定": "Popular-site text: content may change while the delivery contract stays stable",
-    "方法与样本范围": "Methods and sample scope",
-    "各类任务的样本数与实际调用日期": "Task counts and actual call dates by family",
-    "同一次去标签操作会保留文字,但丢掉地址。它可以完成一些正文任务,却不能代替下游判断需要什么。交付目标应决定文本清洗的激进程度,以及是否保留链接。属性一旦从输出中消失,后续再精确的关键词检查也找不回地址。": "The same tag-stripping operation preserves text but discards URLs. It can satisfy some body-text tasks, but it cannot decide what downstream consumers need. The delivery goal must determine how aggressively text is cleaned and whether links must be retained. Once attributes have disappeared from the output, more precise keyword matching cannot recover their URLs.",
-    "这个实验只验证合成输入上的当前提取函数。已保存的 CowAgent 调用没有逐次源响应指纹,也没有把同一响应字节交给多个提取器。因此不能把每一个实测成功或失败都归因到这个函数,也不能从这个实验计算保留文本范围对通过率的贡献。": "This experiment validates only the current extraction function on synthetic input. Saved CowAgent calls do not include a per-call source fingerprint or feed the same response bytes through multiple extractors. The experiment therefore cannot attribute every observed success or failure to this function, or quantify how retained-text scope affects pass rate.",
-    "后备方法能否发挥作用,先取决于系统能否检出主方法的错误。生产环境通常没有每个新 URL 的完整参考,不能直接照搬这份评测的事后评分。页面身份、最小内容和业务必需字段,才是要测的触发条件。如果验收器把看似合理但错误的正文当成成功,再强的后备也不会被调用。": "A fallback helps only if the system detects the primary method's error. Production usually lacks a complete reference for every new URL, so this benchmark's post-hoc scoring cannot be copied directly. Page identity, minimum content, and business-required fields are the trigger conditions to test. If the validator accepts plausible but incorrect body text, a capable fallback is never invoked.",
-    "这也解释了为什么不能把正文选型直接带到文档和媒体交付。PDF 或 DOCX 正文可读,不等于版式或原文件被保留;图片或视频地址可访问,不等于文件已下载或内容已理解。下游要地址,就验收地址;下游要文件,就要求可校验字节。更可读的纯文本不能代替这项检查。": "This also explains why a body-text choice cannot be carried directly into document and media delivery. Usable PDF or DOCX text does not mean layout or the original file was preserved; an accessible image or video URL does not mean the file was downloaded or its content understood. If downstream needs a URL, validate the URL; if it needs a file, require verifiable bytes. More readable plain text cannot substitute for this check.",
-    "剧集榜单的名字和顺序可以更新,页面身份、稳定栏目与说明文字仍然可以核对。条目按要求数量、非空名称和有效详情地址验收。标题正确但列表为空,或条目完整但来自错误栏目,都可能不通过混合契约。可重现的是问题与规则,不是某天的全部答案。": "A show's name and rank may change, while page identity, stable sections, and explanatory copy remain valid checks. Records are checked for the required count, non-empty names, and valid detail URLs. A correct title with an empty list—or complete records from the wrong section—can still fail the mixed contract. What is reproducible is the question and its rules, not every answer from one day.",
-    "通过率衡量已登记的内容与结构要求。只数卡片会让空占位通过;只看字段非空,又可能放过错位或伪造的条目。稳定文字命中与字段完整,也不能证明每个事实准确、实时或无遗漏。只有参考范围明确时才计算文本重合。缺少参考不会抹掉已完成的内容与结构检查,也不把 F1 记成零分。": "Pass rate measures the registered content and structure requirements. Counting cards alone accepts empty placeholders; checking only non-empty fields can accept mismatched or fabricated records. Even stable-text matches and complete fields do not prove every fact is accurate, current, or exhaustive. Use text overlap only when reference scope is explicit. A missing reference does not erase completed content/structure checks or turn F1 into zero.",
-    "独立 HTML 与证据下载": "Standalone HTML and evidence downloads",
-    "正常返回但任务未通过": "Normal response; task failed",
-    "动态网站正文:内容何时可以交付": "Rendered web text: when is content deliverable?",
-    "用于比较排名逆转与提取机制的方法:同一静态网站正文范围,F1 为有指标记录的逐例算术平均": "Methods used to compare ranking reversals and extraction mechanisms: within the same general-web scope, F1 is the arithmetic mean across calls with measured values.",
-    "任务通过 / 调用": "Tasks passed / calls", "平均 F1": "Mean F1", "F1 样本数": "F1 sample count",
-    "当前输入没有与案例及参考校验值完整匹配的初始源文档审计,不能汇总动态内容依赖比例。类别标签既不能证明正文已经在初始 HTML 中,也不能证明必须执行 JavaScript。": "No initial-source audit in the current input fully matches the task and reference checksums, so the share that depends on dynamic content cannot be aggregated. A category label proves neither that body text is already in the initial HTML nor that JavaScript execution is required.",
-    "离线机制实验:纯文本提取函数对合成 HTML 的输出,不计入方法排名": "Offline mechanism experiment: output of the plain-text extractor on synthetic HTML; excluded from method rankings",
-    "合成输入中的信息": "Information in synthetic input", "提取后": "After extraction",
-    "正文": "Body text", "保留": "Retained", "导航": "Navigation", "页脚": "Footer",
-    "链接文字": "Link text", "脚本内容": "Script content", "未保留": "Not retained",
-    "详情 URL(href)": "Detail URL (href)", "图片 URL(src)": "Image URL (src)",
-    "视频 URL(src)": "Video URL (src)", "核对合成输入与实际输出": "Compare synthetic input with actual output",
-    "输入 HTML": "Input HTML", "实验代码、观察结果与源码 SHA-256": "Experiment code, observations, and source SHA-256",
-    "两种方法的结果组合": "Outcome combination", "网页案例数": "Web tasks",
-    "都通过": "Both pass", "仅 CowAgent 通过": "CowAgent only", "仅 Moli 通过": "Moli only",
-    "都未通过": "Neither passes", "互补案例": "Complementary task", "CowAgent 本次结果": "CowAgent result",
-    "Moli 本次结果": "Moli result", "任务范围": "Task family", "任务数": "Tasks",
-    "实际方法数": "Measured methods", "调用开始时间(UTC)": "Call window (UTC)",
-    "本文计算明细": "Article calculations", "下载本文计算明细": "Download article calculations",
-    "下载规范化执行记录": "Download normalized run records", "下载离线提取实验": "Download offline extraction experiment",
-    "Lexmount 中国区": "Lexmount China", "Lexmount 国际": "Lexmount Global",
-    "报告不可靠:评测未完整": "Report unreliable: evaluation incomplete",
-    "动态网站正文:取到页面之后,还要等什么": "Rendered web text: what must happen after retrieving the page?",
-    "内容呈现方式": "Delivery mechanism", "通过 / 已运行": "Passed / run",
-    "Angular 路由+JSON": "Angular routing + JSON", "客户端目录": "Client-rendered catalog",
-    "内嵌数据+延迟呈现": "Embedded data + delayed rendering", "iframe 子文档(不依赖 JS)": "iframe subdocument (no JS required)",
-    "内嵌 JSON 呈现": "Embedded JSON rendering", "异步接口": "Asynchronous API",
-    "React 应用": "React application", "GraphQL 异步接口": "Asynchronous GraphQL API",
-    "接口规范转为可读目录": "API specification converted to a readable catalog",
-    "商品页还有一个会制造假完成的细节:初始页面已有 12 个占位卡片,但卡片文字为空。只等 DOM 数量达标,会把空占位当成加载完成。参考采集要求“数量达标且必需字段非空”,在有上限的等待内取得目标条目,并由独立观察核对完整范围。数量与字段要同时满足,才能把“布局已经出现”与“内容可以交付”区分开。": "The product page exposes another false-completion trap: its initial page contains 12 cards whose text is empty. Waiting only for the DOM count treats placeholders as complete. Reference capture therefore requires both the target count and non-empty required fields within a bounded wait, followed by an independent full-scope observation. Count and field requirements together distinguish visible layout from deliverable content.",
-    "原始输出与当前参考": "Raw output and current reference",
-    "Books to Scrape 的书目页": "The Books to Scrape catalog",
-    "换成": "with", ",再运行统一的": ", then run the shared ", "通过,": "passes, while its",
-    "未提供调用时间": "Call time not provided", "调用日期未提供": "Call date not provided",
-    "抓取接口正常返回之后,正文仍可能缺失、混入导航,或丢掉详情地址。这轮实测从这些差异出发,判断哪些方法值得接入,哪些结论还需要试验。": "After a fetch interface returns normally, body text may still be missing, mixed with navigation, or stripped of detail URLs. These measured differences show which methods merit integration and which conclusions still require experiments.",
-    "当前输入不足以比较 Cloudflare Markdown 与 Exa。": "The current input cannot compare Cloudflare Markdown with Exa.",
-    "当前输入没有可展示的 CowAgent 低 F1 通过案例。": "The current input has no low-F1 passing CowAgent task to show.",
-    "提供各例匹配口径。": "provides each task's matching rules.",
-    "当前输入没有 CowAgent 网页记录。": "The current input has no CowAgent web records.",
-    "当前输入不满足逐例配对条件,不能计算 CowAgent 与 Moli 的离线互补覆盖。": "The current input does not support task-level pairing, so CowAgent and Moli's offline complementary coverage cannot be calculated.",
-    "同一网页的正文与媒体地址是两种交付目标;文字保留完整,地址仍可能丢失。": "Body text and media URLs from the same page are different deliverables; complete text can still omit the URLs.",
-    "成功集并集覆盖了全部案例,仍需验证线上能否识别缺项并触发后备;离线覆盖不能直接当作服务保证。": "The union covers every task, but production still must detect missing content and trigger fallback. Offline coverage is not a service guarantee.",
-    "样本与时间范围:各类任务按各自契约验收,日期不构成同时间受控对照": "Sample and date scope: each family follows its own contract; dates are not a same-time controlled comparison",
-    "判定规则与指标口径": "Acceptance rules and metric definitions",
-    "规范化执行记录": "Normalized run records", "计算明细与复验入口:": "Calculation details and reproduction: ",
-    "复现说明": "Reproduction instructions", "实际输出": "Actual output",
-    "书目页:规则实际检查了什么": "Book catalog: what the rules actually check",
-    "可接受表达": "Accepted expression", "核对本例的书名、价格与库存规则": "Inspect this task's title, price, and stock rules",
-    "Moli 通过而 CowAgent 未通过的网页,逐例回到原始记录": "Web tasks passed by Moli but not CowAgent, linked to original runs",
-    "正常返回;任务未通过": "Normal response; task failed",
-    "这也解释了为什么不能把正文选型直接用于文档与媒体交付。PDF、DOCX 的文字可用,不代表版式或原文件已保留;图片与视频入口可用,也不代表文件已经下载或画面已被理解。若下游需要地址,就应核验地址;若下游需要文件,就必须收到可校验的字节。纯文本输出是否更易读,无法代替这项检查。": "This is also why a body-text choice cannot be applied directly to document and media delivery. Usable PDF or DOCX text does not preserve layout or the original file; an accessible image or video endpoint does not mean the file was downloaded or its content understood. Validate a URL when downstream needs a URL, and require verifiable bytes when it needs a file. Readable plain text cannot replace that check.",
-    "冷启动、缓存、会话与失败重试": "Cold start, cache, session, and failure retry",
-    "进程或浏览器生命周期": "Process or browser lifecycle",
-    "完成条件不同": "Different completion signals",
-    "动态页面等待阶段": "Rendered-page wait stage",
-    "各自正式接口完成后,再用同一任务契约验收": "After each official interface completes, apply the same task contract",
-    "版本与远端服务漂移": "Version and managed-service drift",
-    "二进制或托管后端": "Binary or managed backend",
-    "共同成功样本": "Shared-pass sample",
-    "样本 P95 ms": "Sample P95 (ms)",
-    "任务通过和文字重合分开看": "Read task acceptance separately from text overlap",
-    "有全文指标的网页调用": "Web calls with full-text metrics",
-    "动态页还要判断何时可以交付": "Rendered pages also require a defensible completion point",
-    "结果核验是发布门禁": "Result review is a publication gate",
-    "如何复验": "How to verify",
-    "文章与证据下载": "Article and evidence downloads",
-    "下载独立 HTML": "Download standalone HTML",
-    "计算明细": "Calculation details",
-    "逐次记录": "Per-call records",
-
-    # Dataset index and reproducibility copy.
-    "案例与依据": "Tasks and evidence",
-    "核对依据可点击展开。访问受限不等于网站失效;稳定内容与条目结构按登记契约核验,不固定频繁变化的榜单。动态网站正文按各案例声明的完整范围核验。": "Open any evidence basis for details. Restricted access does not mean a site is down. Stable content and record structure are checked against registered contracts rather than frequently changing lists. Rendered-web body text is judged within each task's declared complete scope.",
-    "该案例中按已登记任务规则判为通过的调用比例;不因缺少全文参考而隐藏任务结果。": "Share of calls passing this task's registered acceptance rules. Task verdicts remain visible even when a complete full-text reference is unavailable.",
-    "全文指标须有完整、可比的参考正文;图片和视频按资源、文件或文字信息核对。": "Full-text metrics require a complete, comparable body reference. Image and video tasks are checked as resources, files, or text metadata.",
-    "按登记的稳定内容锚点、字段、类型、数量及唯一性验收;任务通过率与耗时进入对应主表,不固定当天名单。": "Acceptance uses registered stable content anchors plus fields, types, counts, and uniqueness. Task pass rate and latency enter the corresponding primary table; the day's list membership is not frozen.",
-    "快照范围": "Snapshot scope",
-    "知乎 30 条热榜、大众点评 15 家商户、JustWatch 40 部剧集。仅公开列表字段,账号信息与原始登录页面不发布。": "Zhihu: 30 trending entries; Dianping: 15 merchants; JustWatch: 40 shows. Only list fields are published; account information and original login pages are excluded.",
-    "动态核验边界": "Volatile-content validation boundary",
-    "稳定标题、栏目、说明按登记内容核对;频繁变化的名单和价格只按声明的字段结构、类型、数量及唯一性核验,不证明变化值的事实准确性与实时性。完整参考缺失不影响任务结果展示。": "Stable headings, sections, and descriptions are checked against registered content. Frequently changing lists and prices are checked only for declared field structure, types, count, and uniqueness; this does not establish their factual accuracy or freshness. Missing complete references do not suppress task verdicts.",
-    "参考示例": "Reference examples",
-    "来源快照与独立访问的条件见各案例。访问受限与网站失效分开判断。": "Each task records its source snapshot and independent-access conditions. Restricted access and site unavailability are judged separately.",
-    "方法参考以下公开项目;本报告使用自己的实时源页快照,不是其官方成绩。": "The methodology draws on the following public projects. This report uses its own live source snapshots and does not present official scores from those projects.",
-    "MDN 媒体示例": "MDN media examples",
-    "只有 Kitesurf 的账户级 Cloudflare Browser Run 路线需要供应商账号与凭据;其余六种方法是本地二进制,不需要账号,但仍需安装并明确指定可执行文件。": "Only Kitesurf's account-level Cloudflare Browser Run path requires a provider account and credentials. The other six methods use local binaries and require installation plus an explicitly configured executable, but no provider account.",
-    "通过 Playwright 安装匹配的 Chromium,配置 CHROMIUM_BINARY。": "Install the matching Chromium through Playwright and configure CHROMIUM_BINARY.",
-
-    # Methodology page labels and prose.
-    "怎么测": "How it is measured",
-    "怎样算通过": "What counts as a pass",
-    "结构怎么测": "How structure is measured",
-    "任务契约": "Task contract",
-    "结果与规则绑定": "Verdict-to-rule binding",
-    "完整性与模式": "Integrity and reference modes",
-    "全文诊断独立": "Independent full-text diagnostics",
-    "按任务定义验收规则。同组已测方法使用相同的冻结任务与参考,覆盖数量在各榜单列明。": "Acceptance rules are defined by task. Measured methods within a family use the same frozen tasks and references, and each ranking states its coverage.",
-    "所有方法进入同一个评分入口,但按任务交付物验收,而不是强制使用同一种中间格式。网页与文档正文最终验收可读文本;原始文件任务验收 PDF、DOCX、图片或视频字节;结构任务验收字段和稳定文字。HTML/Markdown 链接与明确配对的结构化名称—链接字段按同一规则核验;不把请求回显或诊断日志当成答案。原始输出与原始适配器状态保留,报告层不按方法名称改写任务结果。": "Every method enters the same scoring pipeline, but acceptance follows the task deliverable rather than a mandatory intermediate format. Web and document body tasks validate readable text; original-file tasks validate PDF, DOCX, image, or video bytes; structure tasks validate fields and stable text. HTML/Markdown links and explicitly paired structured name–URL fields follow the same rules. Request echoes and diagnostic logs are not answers. Raw output and adapter status are retained, and the report layer never rewrites a verdict based on method name.",
-    "从准备发起该次 provider 调用开始,到形成可送入统一评分器的最终答案为止:线程调度、SDK/CLI/HTTP 调用、远端处理与网络下载、浏览器渲染与正文提取、本地解析、统一输入转换,以及文件字节读取、格式识别和 SHA-256 计算均在计时内。": "Timing starts when the provider call is prepared and ends when the final deliverable is ready for the shared scorer. It includes thread scheduling, SDK/CLI/HTTP execution, remote processing and network transfer, browser rendering and body extraction, local parsing, normalized input conversion, file-byte reading, format detection, and SHA-256 calculation.",
-    "数据集与参考加载、provider 配置校验、调用间限速等待、最终答案形成后的证据落盘、统一评分计算、报告汇总与页面生成不计入。供应商上报的服务端耗时只作附加证据,排名使用客户端观测的端到端耗时。": "Dataset and reference loading, provider configuration validation, inter-call rate-limit waits, evidence persistence after the deliverable is ready, shared scoring, report aggregation, and page generation are excluded. Provider-reported server timing is supplementary evidence; rankings use client-observed end-to-end latency.",
-    "这个边界比较的是“拿到本任务最终交付物”所需时间:服务直接返回成品时计完整往返;本地方法若还需下载、渲染或解析,也必须完成后才停表。正文任务中,方法只返回原始 PDF 或 DOCX 时,本地正文解析也计时;原始文件任务则到字节校验完成即停止,不做无关的 Markdown 转换。所有方法使用同一外层计时器和截止预算。超时答案不接收;无法取消的调用会被隔离,整批结果因此不完整并触发不可靠状态,构建产物不得发布。": "This boundary measures time to obtain the task's final deliverable. A service returning a finished result includes the full round trip; a local method stops only after any required download, rendering, or parsing. For body-text tasks, local parsing is timed when a method returns only a raw PDF or DOCX. Original-file tasks stop after byte validation and do not perform an irrelevant Markdown conversion. Every method uses the same outer timer and deadline. Late answers are rejected; non-cancellable calls are isolated, making the batch incomplete and preventing publication.",
-    "完整正文任务除原有核对点外,按参考正文最多五个连续位置检查字符六元组覆盖:每个位置至少达到 60%,通常要求至少四处达到 80%。所有位置一次性做全局容量分配;同一返回片段实例只能计入一个位置,位置排列顺序不影响结论。若页面存在经独立证据确认的合法重排,案例可显式登记不同位置门槛。比较忽略空白和排版符号但保留文字顺序;全文 F1 独立保留实际值。文件任务必须有字节验收,资源 URL 不等于文件。": "In addition to existing checks, complete-body tasks measure six-character-shingle coverage across up to five consecutive reference regions. Every region must reach 60%, and normally at least four must reach 80%. All regions are assigned globally under one capacity constraint: each returned fragment instance can count toward only one region, and region order cannot change the verdict. A task may declare different regional thresholds when independent evidence establishes legitimate reordering. Matching ignores whitespace and presentation marks but retains text order; full-text F1 remains a separate observed value. File tasks require byte validation, and a resource URL is not a file.",
-    "原始快照保留不变;评分参考清除零宽空格、BOM 和软连字符,并检查空正文、乱码、重复核对点及核对点是否存在于参考中。通过检查不等于语义完整性已获证明,正文范围仍以来源记录为准。": "Original snapshots remain unchanged. Scoring references remove zero-width spaces, BOMs, and soft hyphens, and are checked for empty bodies, decoding corruption, duplicate checks, and whether each check exists in the reference. Passing validation does not prove semantic completeness; source records still define body scope.",
-    "各方法使用同一套冻结任务与参考。不同批次的耗时不直接排名;同窗速度子集单独标识。实时网页内容、登录状态和站点策略可能变化,成绩只代表所记录的调用。环境、版本与命令见“环境与复现”。": "All methods use the same frozen tasks and references. Latencies from different batches are not ranked directly; the same-window cohort is identified separately. Live content, login state, and site policy may change, so results represent only the recorded calls. See Reproduction for environment, versions, and commands.",
-    "不把“代码有更新”直接等同于全部重跑。只改展示时重建报告;只改裁判且原始输出足够时离线重算;某个参考变化时重跑该题的全部方法;新增或升级单个实现时重跑该方法的全部题;计时边界变化时废弃并重跑同窗测速。只有全局任务输入、调用边界或所有方法共同依赖发生变化,才需要全量重跑。": "A code change does not automatically require a full rerun. Presentation-only changes rebuild the report; judge-only changes are rescored offline when saved output is sufficient; a changed reference reruns every method for that task; a new or upgraded implementation reruns that method across all tasks; a timing-boundary change invalidates and reruns the same-window cohort. A full rerun is reserved for changes to global task inputs, call boundaries, or dependencies shared by every method.",
-    "完整度=取回的参考词数 ÷ 参考词数;纯净度=匹配的正文词数 ÷ 返回词数;F1=2 × 完整度 × 纯净度 ÷ 两者之和。英文按词、中文按字,统一大小写与 Unicode 字符形式;Markdown 链接只计文字。对有完整参考的全部案例取平均;请求失败计 0,请求成功但任务未通过仍按实际正文评分,没有参考不计算。": "Recall equals recovered reference units divided by reference units; precision equals matched body units divided by returned units; F1 equals 2 × recall × precision ÷ their sum. English is tokenized by word and Chinese by character, with case and Unicode normalized; only Markdown link text is counted. Values are averaged across tasks with complete references. Request failures score 0, successful requests that fail acceptance retain their observed text score, and tasks without references are excluded.",
-    "网页、文档使用独立保存的参考正文;仓库以 README 为准,列表只覆盖指定的已加载范围,不要求无限滚动或跨页翻页。登录页和验证码不能代替目标正文。参考正文自身包含的“404 Not Found”等术语不作为错误页判据。": "Web and document tasks use independently retained body references. Repository tasks target the README; list tasks cover only the declared loaded range and do not require infinite scrolling or pagination. Login and CAPTCHA pages cannot replace the target body. Terms such as “404 Not Found” inside the reference itself are not treated as error-page signals.",
-    "媒体网址只证明找到入口,不代表取得文件。文件任务没有另接下载器代替参评方法完成。": "A media URL proves only that an endpoint was found, not that the file was obtained. File tasks do not use a separate downloader to complete the evaluated method's work.",
-
-    # Review and reproduction pages.
-    "核验时隐藏机器结论和分数,只提供任务契约、标准答案与完整实际输出;分歧回到原始证据裁定,未决非零则禁止发布。任务和输出未变化时沿用原核验;发生变化的调用必须重新盲审。": "Machine verdicts and scores are hidden during review; the reviewer receives only the task contract, reference answer, and complete actual output. Disagreements are adjudicated from original evidence, and any unresolved item blocks publication. Existing reviews may be retained when task and output are unchanged; changed calls require a new blind review.",
-    "独立代理": "Independent agent",
-    "在评测仓库中执行;从明确指定的持久化冻结证据构建,不重新调用浏览器。": "Run in the benchmark repository and build from an explicitly selected persistent frozen-evidence directory; browsers are not called again.",
-    "browser-eval 仓库": "browser-eval repository",
-    "保存当前评测代码、任务定义和独立参考;本报告另提供本轮冻结结果供复核。": " contains the current benchmark code, task definitions, and independent references. This report separately provides the frozen results for this run.",
-    "最终报告纳入": "The final report includes",
-    "次调用。任务与参考冻结后执行;每个方法内部串行,不复用个人登录状态。": " calls. Execution begins after tasks and references are frozen; calls are sequential within each method and never reuse personal login state.",
-    "“事后核验”只说明留存二进制与官方归档完全一致,不改写成当时已经记录了版本。移动 nightly 无法追到不可得的源修订时,只用 SHA-256 标识精确可执行文件。": "“Post-run verification” means only that the retained binary exactly matches an official archive; it does not rewrite history to claim the version was recorded at call time. When a moving nightly cannot be mapped to an unavailable source revision, SHA-256 alone identifies the exact executable.",
-    "本地构建为每个任务、方法和证据生成可独立检查的逻辑路由;这不等于线上部署同样数量的独立 HTML。发布器会把整份报告编译成一个通用 HTML 外壳、共享样式与脚本,以及按内容寻址的压缩数据块。页面切换由前端路由完成,证据只在打开对应页面时读取,超过 256 KiB 的完整文本还需用户主动加载。": "The local build creates an independently inspectable logical route for every task, method, and evidence item; this does not mean the deployment contains the same number of standalone HTML shells. The publisher compiles the report into one reusable shell, shared styles and scripts, and content-addressed compressed data blocks. Client-side routing changes pages, evidence loads only when its route is opened, and complete text over 256 KiB requires explicit user loading.",
-    "发布清单记录逻辑路由数、源文件数、压缩块数和逐文件 SHA-256;发布门禁会重建每个文件并验证内部链接与原始下载字节。这样保留逐项可审计性,同时避免重复页面骨架和一次性加载全部证据。": "The publication manifest records logical routes, source-file count, compressed blocks, and per-file SHA-256. The publication gate reconstructs every file and validates internal links plus original download bytes. This retains item-level auditability without duplicating page shells or loading all evidence at once.",
-    "运行配置:": "Runtime configuration:",
-    ";账号变量名称见本报告“账号”页,实际密钥只放仓库根目录忽略提交的": "; credential variable names are listed on Access & setup, while actual secrets exist only in the ignored repository-root",
-    "。冻结参考、完整重测与核验门禁见": ". Reference freezing, complete reruns, and review gates are documented in",
-    "仓库复现说明": "repository reproduction guide",
-
-    # Analysis controls and failure vocabulary.
-    "同题结果组合": "Same-task outcome comparison",
-    "以方法 A 为基准,可选择一个或多个方法共同比较;方法名可直达该次完整证据。": "Use Method A as the baseline and select one or more methods for a joint comparison. Each method name opens its complete evidence.",
-    "方法 A": "Method A",
-    "比较条件": "Comparison condition",
-    "结果有分歧": "Outcomes differ",
-    "A 成功,其余失败": "A passes; all others fail",
-    "A 失败,其余成功": "A fails; all others pass",
-    "所选方法全部成功": "All selected methods pass",
-    "所选方法全部失败": "All selected methods fail",
-    "仅所选方法成功": "Only the selected methods pass",
-    "任务类型": "Task family",
-    "全部类型": "All families",
-    "对比方法(可多选)": "Methods to compare (multiple selection)",
-    "成功方法": "Passing methods",
-    "失败方法": "Failing methods",
-    "正文不足": "Insufficient body text",
-    "浏览器执行上下文中断": "Browser execution context interrupted",
-
-    # Review-round records. These are public release evidence, not internal logs.
-    "五方法初次逐项复核": "Initial item-level review of five methods",
-    "五种方法的 750 条主结果。": "750 primary results across five methods.",
-    "排版空白、Markdown 链接换行等格式差异造成假失败,也有不完整正文被放行。": "Formatting differences such as whitespace and Markdown link wrapping caused false failures, while some incomplete body text had been accepted.",
-    "统一规范化与正文覆盖规则,不按方法设置评分特例;纠正 43 个假阴性和 1 个假阳性。": "Unified normalization and body-coverage rules without method-specific scoring exceptions; corrected 43 false negatives and 1 false positive.",
-    "无未决判定;当时尚未纳入后续新增的 Moli 版本。": "No unresolved verdicts; later-added Moli versions were not yet in scope.",
-    "六方法路线与盲审复核": "Path and blind review for six methods",
-    "六种方法的 900 条主结果和 120 条同窗测速。": "900 primary results and 120 same-window latency results across six methods.",
-    "Moli 1.1.3 的直接 PDF 和媒体被错误附加 --dump;挑战页、链接排版和飞书表格也存在等价内容误判。": "Direct PDF and media calls for Moli 1.1.3 incorrectly used --dump. Equivalent content was also misjudged on challenge pages, Markdown link layouts, and the Feishu table.",
-    "直接文件改用裸 fetch;统一按任务交付物验收,并对全部输出盲审。": "Changed direct files to bare fetch, aligned acceptance on the required task deliverable, and blind-reviewed every output.",
-    "无未决判定;当时尚未纳入 Moli 1.0.6。": "No unresolved verdicts; Moli 1.0.6 was not yet in scope.",
-    "七方法矩阵复核": "Seven-method matrix review",
-    "加入 Moli 1.0.6 后需要同口径比较;部分高变化网页参考不适合长期复现。": "Adding Moli 1.0.6 required an equivalent comparison, and several highly volatile web references were unsuitable for long-term reproduction.",
-    "三个 Moli 版本统一路线;八题换为不可变来源,live-025 重新观测并重测。": "Aligned the retrieval path across all three Moli versions, moved eight tasks to immutable sources, and re-observed and reran live-025.",
-    "无未决判定。": "No unresolved verdicts.",
-    "document-03 参考稳定性复核": "document-03 reference-stability review",
-    "document-03 的七条结果;其余 1,043 条结果复用已绑定核验。": "Seven document-03 results; the other 1,043 results retain their bound reviews.",
-    "W3C 的短 PDF 地址已返回 Cloudflare 验证页,历史调用与当前参考不能继续直接比较。": "The short W3C PDF URL now returns a Cloudflare verification page, so historical calls can no longer be compared directly with the current reference.",
-    "替换为固定 Git 提交中的三页 PDF;只重跑并盲审该题的七个方法。": "Replaced it with a three-page PDF from a pinned Git commit; reran and blind-reviewed only the seven methods for that task.",
-    "跨会话重建复核": "Cross-session rebuild review",
-    "默认冻结证据、当前报告配置、方法路线元数据和核验绑定。": "Default frozen evidence, current report configuration, method-path metadata, and review bindings.",
-    "默认证据仍指向旧评测,机器可读数据又保留了已淘汰的 Chromium 转换器说明。新会话可能重建出另一份报告。": "Default evidence still pointed to an older benchmark, while machine-readable data retained a retired Chromium converter description. A new session could therefore rebuild a different report.",
-    "默认证据切换为七方法最终证据;增加当前报告配置门禁,并把路线说明与实测实现绑定。": "Changed the default to the final seven-method evidence, added a current-report configuration gate, and bound path descriptions to measured implementations.",
-    "无。": "None.",
-    "版本差异与样本可访问性复核": "Version-difference and sample-accessibility review",
-    "保留正式单次调用和原分数;报告不把两题差距归因为稳定版本回归,也不因当前会话可访问而改写历史结果。京东继续作为会话与网络敏感的热门站点挑战样本,不冒充确定性能力样本。": "Retained the formal single calls and original scores. The report does not attribute the two-task difference to a stable version regression or rewrite historical results because the current session can access the page. JD remains a session- and network-sensitive popular-site challenge rather than a deterministic capability sample.",
-    "版本稳定性需要预先设计的多轮重复实验;本报告的主榜仍是每题一次调用。": "Version stability requires a preregistered repeated-run experiment; this report's primary matrix still uses one call per task.",
-    "Moli 时延回退线索复核": "Moli latency-regression signal review",
-    "issue #66 指向的版本时延差异;四个正式差异较大的代表任务各三轮顺序轮换复测,另以本地固定 HTML 各五轮分离启动与转换开销。": "Version latency differences reported in issue #66: three order-rotated rounds on four representative tasks with large formal differences, plus five local fixed-HTML rounds per version to separate startup and conversion overhead.",
-    "把 issue 定位为需要继续追踪的公网抓取长尾线索,而不是已确认的版本回退;报告补充诊断范围、轮换顺序和本地对照,不改变正式一次调用结果。": "Classified the issue as a public-network tail-latency signal requiring further study, not a confirmed version regression. Added diagnostic scope, rotation order, and local controls without changing formal single-call results.",
-    "现有适配器的原生抓取计时仍同时包含网络、页面等待和浏览器内部处理,尚不能把公网差异拆成单一根因;若要形成产品回退结论,应另建预注册多轮性能实验。": "The current adapter's native-fetch timing still combines network transfer, page waiting, and browser-internal processing, so public-network differences cannot be reduced to one cause. A product-regression conclusion requires a separate preregistered repeated-run performance experiment.",
-    "Moli 时延异常纠正": "Moli latency-anomaly correction",
-    "四个差异最大的代表任务、三个 Moli 版本;三轮轮换共 36 次物理调用,纠正 12 条权威逻辑结果的耗时。": "Four representative tasks with the largest differences across three Moli versions; 36 physical calls in three rotated rounds corrected latency for 12 authoritative logical results.",
-    "旧主矩阵的少量极端耗时会显著扭曲各版本整体均值和 P95;这些差异不能由内容得分或版本固定开销解释。": "A few extreme latencies in the old primary matrix materially distorted each version's overall mean and P95; content scores and fixed version overhead could not explain them.",
-    "固定同一四题和三个版本,统一采用第三轮而非各自最快轮;只替换耗时与阶段计时,正文、哈希、评分和判定保持不变。旧值、36 次调用和选择规则全部保留在纠正回执中。": "Fixed the same four tasks and three versions and selected round three uniformly rather than each version's fastest round. Only elapsed and stage timing changed; body text, hashes, scores, and verdicts remain unchanged. Old values, all 36 calls, and the selection rule are retained in the correction receipt.",
-
-    # Reproduction download labels.
-    "评测集与参考": "Dataset and references",
-    "全部调用记录": "All call records",
-    "分类汇总": "Summary by family",
-    "执行批次": "Execution batches",
-    "版本回溯证据": "Version provenance evidence",
-    "参考修订收据": "Reference-revision receipt",
-    "评分修订收据": "Scoring-revision receipt",
-    "时延纠正收据": "Latency-correction receipt",
-    "请求状态纠正收据": "Request-status correction receipt",
-    "二进制 SHA-256": "Binary SHA-256",
-    "与": "and",
-
-    # Interpretation findings.
-    "Wikipedia 单页实测。原生路径不会虚构 HTML/转换拆分;Cloudflare 的 browser_ms_used 是服务端计费诊断,不等于客户端端到端耗时;超时缺少阶段数据时记为 —。": "Single-page Wikipedia measurement. Native-output paths do not invent an HTML/conversion split. Cloudflare browser_ms_used is a server-side billing diagnostic, not client-observed end-to-end latency; missing stage data on timeout is shown as —.",
-    "远端 Kitesurf 包含公网往返、排队与远端执行;本地方法包含进程启动和本机资源竞争。不同批次的耗时不直接排名;速度结论只使用同一时间窗口、双方共同通过的样本。它衡量客户端拿到合格 Markdown 的时间,不是纯内核 CPU、长期吞吐或 SLA。": "Remote Kitesurf includes public-network round trips, queueing, and remote execution; local methods include process startup and local resource contention. Latency from different batches is not ranked directly. Speed conclusions use only tasks that both methods passed in the same time window. The metric is time for the client to receive acceptable Markdown—not isolated engine CPU time, sustained throughput, or an SLA.",
-    "每次新进程或新上下文,不复用个人会话;本轮每题一次且未重试。后续若预先启用一次失败重试,保留两次物理证据并固定采用最后一次,禁止择优": "Use a fresh process or context for every call without reusing personal sessions. This run made one call per task with no retries. A future run may predeclare one failure retry, but must retain both physical calls and use the final response rather than selecting the better one.",
-    "计时外记录批次前后版本与 SHA-256,身份变化直接失败;每次调用绑定该身份。事后恢复与运行时记录分开披露,nightly 无法映射 commit 时不猜测": "Record versions and SHA-256 before and after each batch outside the timer. Identity drift fails the batch, and every call binds that identity. Post-run recovery is disclosed separately from runtime records; no commit is guessed when a nightly cannot be mapped.",
-    "速度仅使用 8/20 个全部 7 种方法共同通过的同窗任务;这是小样本探索性比较。主结果中的跨批次耗时只作逐次诊断,不参与此表排名。": "Latency uses only the 8/20 same-window tasks passed by all 7 methods. This is an exploratory small-sample comparison. Cross-batch latency in the primary results remains a per-call diagnostic and does not enter this ranking.",
-    "同窗速度子集:预先声明 20 题,7 种方法共同通过 8 题;所有方法使用完全相同的成功样本。样本少于 20 时,P95 只作本次样本诊断,不视为稳定尾延迟。": "Same-window latency cohort: 20 tasks were declared in advance and all 7 methods passed 8; every method is compared on exactly the same successful sample. With fewer than 20 observations, P95 is only a diagnostic for this sample and not a stable tail-latency estimate.",
-    "逐次词级指标的算术平均,包含已测失败调用;未通过任务不会把已有重合归零。完整度与纯净度均受已声明参考范围约束。": "Arithmetic means of per-call text metrics include measured calls that failed task acceptance; a failed task does not erase observed overlap. Both recall and precision are bounded by the declared reference scope.",
-    "完整度、纯净度与 F1 是参考范围内的词级诊断,不证明逐句语义或实时事实。任务未通过时,已取回的有效文字也不会被强制记为零;没有完整参考的题显示未测。": "Recall, precision, and F1 are text-overlap diagnostics within the reference scope; they do not establish sentence-level meaning or current facts. Useful text recovered by a failed task is not forced to zero, and tasks without complete references are shown as unmeasured.",
-    "动态网页逐例验收。延迟、异步取数与 iframe 分别检查;固定 DOMContentLoaded 不是内容完成判据。": "Rendered-web tasks are accepted individually. Delayed presentation, asynchronous data, and iframes are checked separately; a fixed DOMContentLoaded event is not evidence that content is complete.",
-    "主结果全部输出 1050/1050;同窗测速全部输出 140/140;未决 0。成功项与失败项均已逐条独立核验,轮次与修正见“核验记录”。": "Primary outputs are complete at 1050/1050; same-window latency outputs are complete at 140/140; unresolved items: 0. Both passing and failing results received independent item-level review. Review rounds and corrections appear on the Review page.",
-    "报告保存任务 ID、原始输出、哈希、运行 ID、方法版本或版本缺口、参数、计时与参考来源。正文分区页直接展示每段标准答案、最低要求、实际命中数和缺失词。环境、批次、代码与命令集中在“环境与复现”,不在多页重复。": "The report retains task ID, raw output, hashes, run ID, method version or version gap, parameters, timing, and reference source. Body-region pages show each reference region, minimum requirement, observed matches, and missing text directly. Environment, batches, code, and commands are centralized on Reproduction rather than repeated across pages.",
-
-    # Method overview and provider profiles.
-    "参评方法的官方能力、价格、接口与开源采用;实测表现见各方法执行记录。": "Official capabilities, pricing, interfaces, and open-source adoption for the evaluated methods. Measured performance is reported in each method's execution records.",
-    "Kitesurf 使用 Cloudflare 账户级 Browser Run /markdown API;耗时包含公网往返、远端排队与执行。": "Kitesurf uses Cloudflare's account-level Browser Run /markdown API. Latency includes public-network round trips, remote queueing, and execution.",
-    "Lightpanda、Obscura 与三个 Moli 版本的普通网页使用 CLI 正文输出。三个 Moli 版本的直接文档和媒体 URL 均使用裸 fetch:正文任务在预算内解析,文件任务校验字节。": "Lightpanda, Obscura, and all three Moli versions use CLI body output for ordinary web pages. Direct document and media URLs use bare fetch for every Moli version: body tasks parse within the deadline, while file tasks validate bytes.",
-    "Chromium 152 由 Playwright 取完整渲染 DOM,再用 node-html-markdown 2.0.0 转换。七种方法都不调用模型。": "Playwright retrieves the complete rendered DOM from Chromium 152, then node-html-markdown 2.0.0 converts it. None of the seven methods calls a model.",
-    "截至 2026-09-07,Cloudflare 尚未发布 Kitesurf 源码或自行部署包;本轮只能通过 Cloudflare 账号版 Browser Run 远程测试,不能把它当作本地开源浏览器。": "As of 2026-09-07, Cloudflare has not released Kitesurf source code or a self-hosting package. This benchmark can test it only remotely through account-level Browser Run, so it is not treated as a local open-source browser.",
-    "Cloudflare 官方博客表示计划在准备好后开源,并希望允许客户部署到自己的账户。这是未来计划,不是当前已有能力。": "Cloudflare's official blog states an intention to open-source Kitesurf when ready and to let customers deploy it in their own accounts. That is a future plan, not a current capability.",
-    "普通网页使用 fetch --dump markdown;直接文档和媒体使用裸 fetch。原始文档的本地正文解析计入耗时,文件任务直接校验字节。": "Ordinary web pages use fetch --dump markdown; direct documents and media use bare fetch. Local body extraction from an original document is included in latency, while file tasks validate bytes directly.",
-    "下表介绍本轮实际调用的实现;方法名称可打开其全部已测案例。各组覆盖数量以执行记录为准,功能范围以本轮配置为准。": "The table describes the implementations actually called in this benchmark. A method name opens all of its measured tasks. Execution records determine coverage, and the measured configuration determines capability scope.",
-
-    # Methodology examples (illustrative report prose, not saved references).
-    "打开 Books to Scrape 首页,取得本页书目正文。": "Open the Books to Scrape home page and retrieve the book-list body on that page.",
-    "独立保存页面完整书目;核对书名、价格与库存。例如首本 A Light in the Attic,£51.77,In stock。": "Independently retain the complete page listing and check title, price, and availability—for example, the first item A Light in the Attic, £51.77, In stock.",
-    "对照完整正文计算完整度、纯净度与 F1;另检查长度、关键事实及错误页标记。": "Calculate recall, precision, and F1 against the complete body; separately check length, required facts, and error-page signals.",
-    "关键事实与正文规则全部通过。通过不等于没有漏字,仍需看全文指标。": "All required facts and body rules pass. Acceptance does not mean every character was recovered; consult the full-text metrics.",
-    "读取各案例声明的初次加载范围。例如 GraphQL 首批 20 条评论、React 目录 16 个商品、Swagger 的 20 条接口摘要,或 iframe 中的 14 个分类名称。各案例独立评分。": "Read each task's declared initial-load scope—for example, the first 20 GraphQL reviews, 16 React catalog products, 20 Swagger endpoint summaries, or 14 category names inside an iframe. Each task is scored independently.",
-    "独立观察初次加载范围,登记稳定内容锚点(稳定标题、栏目、说明)及条目数量与字段结构,例如商品名称、数值价格、可见分期字段。仅频繁变化的条目标题、价格不设为永久答案,稳定内容仍核对。": "Observe the initial-load scope independently and register stable content anchors (stable headings, sections, and descriptions) plus record count and field structure, such as product name, numeric price, and visible financing fields. Only frequently changing item titles and prices are excluded from permanent answers; stable content remains checked.",
-    "核对稳定内容锚点,并检查各条目的字段归属、数量、唯一性与类型;支持声明的字段顺序和常见排版。完整、可比的文本参考可用于独立重合诊断,不等于变化值的事实准确性或实时性判定。": "Check stable content anchors together with each record's field ownership, count, uniqueness, and types, allowing declared field order and common layouts. A complete comparable text reference may support a separate overlap diagnostic, but does not establish the factual accuracy or freshness of volatile values.",
-    "请求成功且本例全部正文规则通过。限定首批记录,不暗含滚动、翻页或点击操作;缺少计划调用或判定会使报告不完整,不能发布。": "The request succeeds and every body rule for the task passes. The contract covers only the declared initial records and does not imply scrolling, pagination, or clicks. A missing planned call or verdict makes the report incomplete and blocks publication.",
-    "以 JustWatch 为例取得声明数量的不同剧集及名称、详情入口;任务结果与耗时进入热门网站主表。缺少完整参考只影响全文指标,不隐藏任务结果。": "For a JustWatch example, retrieve the declared number of distinct shows with names and detail endpoints. Task verdict and latency enter the popular-site primary table. A missing complete reference affects only full-text metrics and does not hide the verdict.",
-    "独立页面提供稳定内容锚点与条目结构要求:剧集名称和本站详情入口,不固定当天的剧集名单。": "An independent page supplies stable content anchors and record-structure requirements: show name and a same-site detail endpoint. The day's show list is not frozen.",
-    "至少 3 个有名称的本站详情链接,按地址去重。仅网址、导航按钮和登录提示不计入;频繁变化的条目名称、名次、价格或 ID 不固定为旧快照取值,不免除登记的稳定内容核对。": "Require at least 3 named same-site detail URLs, deduplicated by address. Bare URLs, navigation buttons, and login prompts do not count. Frequently changing names, ranks, prices, and IDs are not frozen to old snapshot values, but registered stable content is still required.",
-    "稳定内容锚点与必需字段、类型、唯一性和数量全部通过。通过不证明变化值的事实准确性或实时性;缺少完整、可比的参考时完整度、纯净度和 F1 为 —。": "Stable content anchors and every required field, type, uniqueness, and count check pass. Acceptance does not establish the factual accuracy or freshness of volatile values; recall, precision, and F1 are — without a complete comparable reference.",
-    "稳定内容锚点与必需字段、类型、唯一性和数量全部通过。通过不证明变化值的事实准确性或实时性;缺少完整、可比的参考时完整度、纯净度和 F1 为 —。查看参考与实测 →": "Stable content anchors and every required field, type, uniqueness, and count check pass. Acceptance does not establish the factual accuracy or freshness of volatile values; recall, precision, and F1 are — without a complete comparable reference. View reference and measured results →",
-    "查看参考与实测 →": "View reference and measured results →",
-    "以 Attention Is All You Need PDF 为例,读取整篇论文;另测 DOCX、TXT 和嵌入文档页面。": "Use the Attention Is All You Need PDF as an example and read the complete paper; DOCX, TXT, and embedded-document pages are tested separately.",
-    "从原始文件全部页面、段落和表格独立提取正文,并保存原文件。": "Independently extract body text from every page, paragraph, and table in the original file, and retain the file.",
-    "输入原始文档 URL,直接使用参评浏览器官方接口返回的正文。只返回 PDF、DOCX 等原始字节时,本题不通过;评测端不再替它解析。取得可读正文后停表,再对照全文计算完整度、纯净度与 F1,并检查标题、核心术语及章节。": "Provide the original document URL and use the body text returned by the browser's official interface. A task fails if the method returns only raw PDF, DOCX, or other file bytes; the evaluation harness does not parse the file on its behalf. Stop timing once readable body text is available, then compare it with the full reference text for recall, precision, and F1, and check the title, key terms, and sections.",
-    "必须返回文档正文,只返回论文下载地址不通过。": "The document body is required; a paper download URL alone fails.",
-    "必须返回文档正文,只返回论文下载地址不通过。查看本例完整参考与逐方法结果 →": "The document body is required; a paper download URL alone fails. View the complete reference and per-method results →",
-    "查看本例完整参考与逐方法结果 →": "View the complete reference and per-method results →",
-    "以 MDN 响应式图片页为例,找到指定正文图片;输入直接 PNG 时交付图片文件。": "Use the MDN responsive-images page to locate specified body images; for a direct PNG input, deliver the image file.",
-    "含图页使用从源页保存的图片地址清单;直接图片使用原文件与校验值。": "Image-bearing pages use an image-URL manifest retained from the source page; direct images use the original file and checksum.",
-    "逐项匹配允许的图片地址;直接文件核对实际字节的格式特征、长度与目标 SHA-256。": "Match each accepted image URL. For direct files, validate the actual bytes by format signature, length, and target SHA-256.",
-    "找齐指定入口,或实际返回文件;只复述 PNG 地址不算取得文件。本榜不测“看懂图片”。": "Return every specified endpoint or the actual file, as required. Repeating a PNG URL does not mean the file was obtained. This ranking does not test image understanding.",
-    "找齐指定入口,或实际返回文件;只复述 PNG 地址不算取得文件。本榜不测“看懂图片”。查看图片参考与结果 →": "Return every specified endpoint or the actual file, as required. Repeating a PNG URL does not mean the file was obtained. This ranking does not test image understanding. View image references and results →",
-    "查看图片参考与结果 →": "View image references and results →",
-    "找到网页中的播放入口、交付直接视频文件,或读取标题与发布者。": "Find playback endpoints in a page, deliver a direct video file, or read title and publisher metadata.",
-    "分别保存源页播放地址、原视频文件或可见标题与发布者,三种任务分开核对。": "Retain source-page playback URLs, original video files, or visible title and publisher as appropriate; the three deliverables are checked separately.",
-    "例如 MDN 视频页匹配目标播放地址;信息页核对文字事实;直接 MP4 核对实际字节的格式特征、长度与目标 SHA-256。": "For example, match target playback URLs on an MDN video page, check textual facts on a metadata page, and validate a direct MP4 by byte signature, length, and target SHA-256.",
-    "完成指定任务;找到播放地址不等于拿到视频,更不等于能识别画面或字幕。": "Complete the specified deliverable. Finding a playback URL is not obtaining the video, much less understanding frames or subtitles.",
-    "完成指定任务;找到播放地址不等于拿到视频,更不等于能识别画面或字幕。查看视频参考与结果 →": "Complete the specified deliverable. Finding a playback URL is not obtaining the video, much less understanding frames or subtitles. View video references and results →",
-    "查看视频参考与结果 →": "View video references and results →",
-    "没有 F1 最低分门槛": "No minimum F1 threshold",
-    "8 次请求成功 → 请求成功率 80%": "8 successful requests → 80% request success rate",
-    "6 次任务通过 → 任务通过率 60%": "6 task passes → 60% task pass rate",
-    "变更记录": "Changelog",
-    "评分规则修订收据": "Scoring-rule revision receipt",
-    "参考答案修订收据": "Reference-answer revision receipt",
-    "按声明范围核对稳定内容锚点(稳定标题、栏目、说明)和条目数量、必需字段、唯一性和字段类型。锚点会明确标为必需或诊断:必需锚点参与验收,诊断锚点只核对抓取范围。频繁变化的名单、条目标题、价格和名次只按声明的结构、类型与数量核验,不固定旧快照取值;这不等于证明这些变化值真实或最新。": "Within the declared scope, validate stable content anchors (stable headings, sections, and descriptions), record count, required fields, uniqueness, and field types. Each anchor is marked as required or diagnostic: required anchors affect acceptance, while diagnostic anchors only check retrieval scope. Frequently changing lists, item titles, prices, and ranks are checked by declared structure, type, and count rather than old snapshot values; this does not establish that those values are accurate or current.",
-    "任务结果对应已登记的内容锚点、字段要求与数量范围;保留原始输出及评分规则,便于逐项复核。": "Each task verdict binds to registered content anchors, field requirements, and count range. Raw output and scoring rules are retained for item-level verification.",
-    "原始输出哈希和证据完整性检查保留。已验证的 stable_contract 按数据不变量验收,不依赖快照时效;exact_snapshot 模式仍可按其协议检查参考。两种模式不能混用,也不声称所有调用满足同一采集窗口。": "Raw-output hashes and evidence-integrity checks are retained. A validated stable_contract uses data invariants and does not depend on snapshot age; exact_snapshot references remain governed by their own protocol. The modes cannot be mixed, and the report does not claim one capture window for every call.",
-    "标为必需的稳定内容锚点参与验收;诊断锚点只核对抓取范围。": "Stable content anchors marked as required affect acceptance; diagnostic anchors only check retrieval scope.",
-    "完整度、纯净度与 F1 必须有完整参考,且与输出的目标范围和内容具有可比性;不能用条目数量推算全文 F1。缺少完整、可比的参考显示 —(全文指标未评测),任务通过率和耗时仍保留。标为必需的稳定内容锚点参与验收;诊断锚点只核对抓取范围。文本重合不证明变化值的事实准确性或实时性,名单变化本身不使稳定任务契约过期。": "Recall, precision, and F1 require a complete reference comparable in target scope and content; record count cannot imply full-text F1. Without such a reference, full-text metrics are shown as — while task pass rate and latency remain. Stable content anchors marked as required affect acceptance; diagnostic anchors only check retrieval scope. Text overlap does not establish factual accuracy or freshness, and list changes alone do not expire a stable task contract.",
-    "任务结果对应已登记的必需锚点、字段要求与数量范围;诊断锚点另行展示。保留原始输出及评分规则,便于逐项复核。": "Task results are bound to registered required anchors, field requirements, and count ranges; diagnostic anchors are shown separately. Raw outputs and scoring rules are retained for item-by-item review.",
-
-    # Current release-note entries.
-    "Codex 独立强推理代理": "Codex independent high-reasoning agent",
-    "任务运行时默认模型": "task runtime default model",
-    "高": "high",
-    "和": "and",
-    "标题 F1": "Title F1",
-    "完整度 · 纯净度 · F1": "Recall · precision · F1",
-    "版本稳定性需要预先设计的多轮重复实验;本报告的当前主榜没有启用失败重试。": "Version stability requires a predesigned repeated-trial experiment; failure retries were not enabled for the current primary ranking.",
-    "请求成功,并同时满足本例最低字符数、全部必需核对点、显著标记及结构要求;不得命中非参考正文自身包含的错误页标记。完整度、纯净度和 F1 另算,不作为通过门槛。 已登记正文覆盖规则的案例,还须在最多 5 个参考区域中逐区达到 80% 词语计数覆盖,不能只返回标题或开头。": "The request must succeed and satisfy the task's minimum length, every required check, salient markers, and structural requirements; it must not match error-page markers outside the reference body itself. Recall, precision, and F1 are calculated separately and are not acceptance thresholds. Tasks with a registered body-coverage rule must also reach 80% token-count coverage in each of up to 5 reference regions; returning only a title or opening excerpt is insufficient.",
-    "核验初次加载范围:稳定内容锚点(稳定标题、栏目、说明)与条目的必需字段、数量、唯一性和类型共同决定任务通过。频繁变化的名单、条目标题和价格按声明的结构与类型核验,不固定旧快照取值。HTTP 页面壳不通过;渲染、内嵌数据和公开 API 均可作为实现路径。全文质量仅在有完整、可比的参考时另算。 React · 商品与分期信息 的字段覆盖要求:分期至少 15 条、配送至少 13 条,各归属于完整且唯一的记录。": "Initial-load acceptance combines stable content anchors (stable headings, sections, and explanations) with each record's required fields, count, uniqueness, and types. Frequently changing lists, item titles, and prices are checked against declared structure and types rather than frozen snapshot values. An HTTP shell alone fails; rendering, embedded data, and public APIs are all valid implementation routes. Full-text quality is calculated separately only when a complete comparable reference exists. For React · 商品与分期信息, field coverage requires at least 15 installment entries and 13 delivery entries, each belonging to a complete unique record.",
-    "请求成功,取得至少 3 条具名详情链接,字段完整、类型正确、条目不重复,链接符合目标站点规则;有独立内容证据的稳定标题、栏目、说明还须命中。频繁变化的名单和价格不固定旧值。通过表示满足上述取数要求,不证明变化值真实或最新。": "The request must succeed and return at least 3 named detail links with complete fields, correct types, unique records, and URLs that follow the target-site rules. Stable headings, sections, and descriptions with independent evidence must also match. Frequently changing lists and prices are not frozen to old values. A pass means these retrieval requirements were met; it does not establish that volatile values are accurate or current.",
-    "文档正文采用与网页相同的文字验收规则:请求、最低长度、全部核对点及错误页检查满足;不是只拿到下载链接。F1 另算,不作为通过门槛。 已登记正文覆盖规则的案例,还须在最多 5 个参考区域中逐区达到 80% 词语计数覆盖,不能只返回标题或开头。": "Document body text uses the same textual acceptance rules as web pages: the request, minimum length, every required check, and error-page checks must pass; obtaining only a download link is insufficient. F1 is calculated separately and is not an acceptance threshold. Tasks with a registered body-coverage rule must also reach 80% token-count coverage in each of up to 5 reference regions; returning only a title or opening excerpt is insufficient.",
-    "150 个任务 · 7 种方法 · 1050 次纳入结果 · 2026-09-04 07:53:04 至 2026-09-08 09:51:08(UTC)": "150 tasks · 7 methods · 1050 included results · 2026-09-04 07:53:04 to 2026-09-08 09:51:08 (UTC)",
-    "网页正文以可读 Markdown 为目标:Kitesurf 使用 Cloudflare 账号版 Browser Run": "Readable Markdown is the target for web-page body text: Kitesurf uses the account-level Cloudflare Browser Run",
-    ";Lightpanda、Obscura 与三个 Moli 版本使用本地原生 Markdown;Chromium 取完整渲染 DOM 后转换。直接文档或媒体不强制先转 Markdown:三个 Moli 版本使用裸": "; Lightpanda, Obscura, and the three Moli versions use native local Markdown; Chromium converts the complete rendered DOM. Direct documents and media are not forced through Markdown first: the three Moli versions use a bare",
-    ",正文题在同一次计时内从确实取得的 PDF/DOCX 提取文字,文件题直接校验原始字节。所有路线最终进入同一套任务裁判。": "; for body-text tasks, text is extracted from an actually retrieved PDF/DOCX within the same timed call, while file tasks validate the original bytes directly. Every route ultimately enters the same task judge.",
-    "满足该例全部验收规则的次数 ÷ 已有验收证据的任务。段落、列表、字段都属于内容要求;": "Calls satisfying every acceptance rule divided by tasks with usable acceptance evidence. Paragraphs, lists, and fields all count as content requirements;",
-    "。请求失败计入分母;任何计划调用缺失或无法判分时,整份报告标为不可靠并禁止发布。": ". Request failures remain in the denominator. If any planned call is missing or cannot be judged, the entire report is marked unreliable and cannot be published.",
-    "当前成绩包含对保存输出的离线重算;文件任务另在 2026-09-04 真实补测,选取 36 次新调用替换原缺测项,详情见": "Current scores include offline rescoring of saved outputs. File tasks were also rerun on 2026-09-04, with 36 new calls replacing previously missing measurements; see",
-    "只列本报告方法的修订。其余历史调用的输出与计时保留,不能把跨批次视图当成同时重跑。": "lists only corrections for methods in this report. Outputs and timings for all other historical calls are retained; this cross-batch view must not be presented as a simultaneous rerun.",
-    "。Moli 时延纠正只替换固定四题、三个版本的 12 个耗时值,并统一取三轮配对实验的最终轮;旧耗时和全部 36 次物理调用保留在": ". The Moli latency correction replaces only 12 latency values for the same four tasks across three versions, consistently using the final round of a three-round paired experiment. Previous timings and all 36 physical calls remain in",
-    "中。其余历史调用的输出与计时保留,不能把跨批次视图当成同时重跑。": ". Outputs and timings for all other historical calls are retained; this cross-batch view must not be presented as a simultaneous rerun.",
-    "记录输入、输出哈希、判定变化及供应商调用数,不把新规则伪装成原调用时的运行配置。": "record inputs, output hashes, verdict changes, and provider-call counts without presenting later rules as the original run configuration.",
-    "关键事实按规范化后的可见文本机械匹配:统一 Unicode、大小写及排版空白,菜单路径如 Settings > AI 与 Settings>AI 等价;忽略 Markdown 链接地址、行内代码与加粗标记。一个已被完整记录边界约束的字段允许丢失内联元素间空白,例如标签列表": "Key facts are matched mechanically against normalized visible text: Unicode, letter case, and layout whitespace are normalized, so menu paths such as Settings > AI and Settings>AI are equivalent; Markdown link destinations, inline-code markers, and emphasis markers are ignored. A field whose complete record boundary is known may omit whitespace between inline elements, so the tag list",
-    "内容等价;相邻独立链接仍按独立词段核对,不猜测跨记录的粘连分词。保留数字、正负号、否定词、标点和英文词边界。不使用大模型或模糊相似度判定通过。": "is content-equivalent. Adjacent independent links are still checked as separate token segments, with no guessed segmentation across records. Numbers, signs, negation, punctuation, and English word boundaries are preserved. Acceptance never relies on an LLM or fuzzy similarity.",
-
-    # Capability, pricing, and adoption copy.
-    "核对日期:2026-09-11。托管服务按官方文档;内置工具按固定源码。此处说明可选能力,": "Checked 2026-09-11. Managed services are described from official documentation and built-in tools from pinned source. These are optional capability declarations,",
-    "不是实测成绩": "not measured scores",
-    ";没有启用的选项不补算进榜单。": "; options not enabled in this run are not retroactively counted in the ranking.",
-    "支持 / 需配置": "Supported / configuration required",
-    "有对应实现或契约;需配置表示要启用额外模式。": "An implementation or contract exists; configuration required means an additional mode must be enabled.",
-    "仅链接 / 有限支持": "Links only / limited support",
-    "仅返回地址,或只覆盖指定输入、正文区域。": "Returns only URLs, or supports only specified inputs or body regions.",
-    "无此能力 / 未声明": "Unavailable / not documented",
-    "前者有明确实现边界;后者是官方资料不足,不能当作不支持。": "Unavailable denotes an explicit implementation boundary; not documented means the official material is insufficient and must not be read as unsupported.",
-    "内容与资源": "Content and resources",
-    "运行与接入": "Runtime and access",
-    "能力声明不是实测任务成绩;任务通过按登记的稳定内容锚点与字段结构等规则判定,不证明频繁变化值的事实准确性或实时性。文档列指 PDF / Office 中的文字;普通 TXT 不等于文档解析。图片、音视频列说明网页资源取得方式,": "Capability declarations are not measured task scores. Task acceptance follows registered stable-content anchors, field structure, and related rules; it does not establish that volatile values are accurate or current. The document column means text from PDF / Office files; plain TXT is not document parsing. The image and audio/video columns describe how page resources can be obtained,",
-    "不代表视觉理解或转写": "not visual understanding or transcription",
-    ";直接媒体 URL 能否取得字节看原文件列。原文件不是页面正文、截图或下载链接本身。": "; byte delivery for direct media URLs is reported in the original-file column. An original file is not page body text, a screenshot, or a download link.",
-    "方法 / 依据": "Method / basis",
-    "图片资源": "Image resources",
-    "音视频资源": "Audio and video resources",
-    "直接取得 Moli 原生 Markdown": "Retrieve Moli native Markdown directly",
-    "直接取得 Moli 原生 Markdown;直接 PDF/DOCX 不提供可读正文": "Retrieve Moli native Markdown directly; direct PDF/DOCX inputs do not yield readable body text",
-    "通过 Moli 原生执行接口读取已加载内容;表达式只由公开任务类型决定": "Read loaded content through Moli's native execution interface; expressions are selected only by public task type",
-    "Markdown 正文统一使用 Moli 默认 done 完成方式": "Markdown body uses Moli's default done completion mode",
-    "普通、动态、热门、风控和直接文档正文统一使用 Moli 原生 Markdown。文件使用原生 CDP 响应或同一二进制的原始附件接口。图片、视频资源和嵌入文档只通过 Moli 原生执行接口读取已加载内容,不另抓模块或下载文档后外部解析。最终网址仅作诊断,不决定正文是否通过;失败后不切换格式或借用其他 provider。": "Regular, dynamic, popular-site, risk-control, and direct-document body tasks use Moli's native Markdown. Files use native CDP responses or the same binary's raw attachment interface. Image and video resources and embedded documents use only Moli's native execution interface to inspect loaded content; the runner does not fetch modules separately or parse downloaded documents externally. The final URL is diagnostic only and does not determine whether body extraction passes. A failure does not switch format or borrow another provider.",
-    "普通、动态、热门、风控及 HTML/TXT 文档正文使用 Moli 原生 Markdown。直接 PDF/DOCX 仍调用同一原生接口;接口没有交付可读正文时按失败记录,评测端不下载后解析。文件使用原生 CDP 响应或同一二进制的原始附件接口。图片、视频资源和嵌入文档只通过 Moli 原生执行接口读取已加载内容。最终网址仅作诊断,不决定正文是否通过;失败后不切换格式或借用其他 provider。": "Regular, dynamic, popular-site, risk-control, and HTML/TXT document-body tasks use Moli's native Markdown. Direct PDF/DOCX inputs still use that native interface; if it does not return readable body text, the task fails and the benchmark does not download and parse the file. Files use native CDP responses or the same binary's raw attachment interface. Image and video resources and embedded documents use only Moli's native execution interface to inspect loaded content. The final URL is diagnostic only and does not determine whether body extraction passes. A failure does not switch format or borrow another provider.",
-    "CLI · 按任务预选 Markdown、DOM eval 或裸响应路线": "CLI · preselect Markdown, DOM eval, or bare-response routes by task",
-    "CLI · 按任务预选 Markdown、DOM eval 或原始响应路线": "CLI · preselect Markdown, DOM eval, or raw-response routes by task",
-    "CLI · 按任务预选 Markdown、JSON 诊断、DOM eval 或裸响应路线": "CLI · preselect Markdown, JSON diagnostics, DOM eval, or bare-response routes by task",
-    "Moli 一次性 fetch:普通正文用 Markdown,挑战题用 JSON 返回最终 DOM、状态和跳转诊断后在同一截止内本地转 Markdown;不调用模型。": "Moli performs one fetch per task: ordinary body tasks use Markdown, while challenge tasks return final-DOM JSON, status, and redirect diagnostics before local Markdown conversion within the same deadline; no model is called.",
-    "stdout 文本/JSON/字节;stderr 独立诊断": "Text, JSON, or bytes on stdout; separate diagnostics on stderr",
-    "每次启动独立进程;使用预声明的有界完成条件": "Starts a separate process for each call and uses predeclared bounded completion conditions",
-    "moli fetch;普通正文使用 Markdown,挑战题使用 JSON 最终 DOM 与网络诊断,动态/资源/嵌入文档使用有界 DOM eval,直接文档与媒体使用裸 fetch 自动响应": "moli fetch; ordinary body tasks use Markdown, challenge tasks use final-DOM JSON with network diagnostics, dynamic/resource/embedded-document tasks use bounded DOM eval, and direct documents and media use automatic bare responses",
-    "普通正文": "Ordinary body text",
-    "动态 / 资源页 / 嵌入文档": "Dynamic / resource-page / embedded-document tasks",
-    "一次返回有界记录文本、结构化 URL 或页面运行时文档": "Returns bounded record text, structured URLs, or a page-runtime document in one call",
-    "一次返回有界记录文本、结构化 URL 或页面运行时文档字节": "Returns bounded record text, structured URLs, or page-runtime document bytes in one call",
-    "使用预声明的生命周期或动态完成条件": "Uses the predeclared lifecycle or dynamic completion condition",
-    "普通网页使用预声明的生命周期或动态完成条件": "Ordinary pages use a predeclared lifecycle or dynamic completion condition",
-    "毫秒;导航、HTTP、进程与本地文档解析共享同一端到端预算": "Milliseconds; navigation, HTTP, process execution, and local document parsing share one end-to-end budget",
-    "默认启用;仅对冻结公开测试域名的精确白名单省略": "Enabled by default; omitted only for an exact allowlist of frozen public benchmark hosts",
-    "普通正文使用 Markdown;动态、资源页和嵌入文档使用一次有界 DOM eval;直接 PDF、DOCX 与媒体文件在调用前选择不带 --dump 和页面等待参数的原始响应路线。直接文档正文在同一截止预算内解析,文件任务按统一字节规则验收;不在失败后切换格式或借用其他 provider。": "Ordinary body tasks use Markdown. Dynamic, resource-page, and embedded-document tasks use one bounded DOM eval. Direct PDF, DOCX, and media inputs preselect a raw-response route without --dump or page-wait flags. Direct-document text is parsed within the same deadline, and file tasks use shared byte-level validation; no post-failure format switch or cross-provider fallback is used.",
-    "普通正文使用 Markdown;动态完成条件与正文提取范围分离。直接 PDF 与媒体使用不带 --dump 的原始响应路线;DOCX 因当前 Moli CLI 的 OOXML 误分类,在同一次 CLI 运行中由页面运行时二次 fetch 原始字节并明确披露。文档解析计入同一截止预算;私网保护默认开启,只对冻结公开测试域名使用精确白名单;不在失败后切换格式或借用其他 provider。": "Ordinary body tasks use Markdown, and dynamic completion is separate from extraction scope. Direct PDF and media inputs use raw responses without --dump. Because the current Moli CLI misclassifies OOXML, DOCX tasks make a disclosed second fetch for the original bytes from the page runtime within the same CLI invocation. Document parsing remains inside the shared deadline. Private-network protection stays enabled by default, with exact exceptions only for frozen public benchmark hosts; no post-failure format switch or cross-provider fallback is used.",
-    "普通正文使用 Markdown;挑战题使用一次 JSON 请求返回最终 DOM、HTTP 状态、最终 URL 与有界网络诊断,并在同一截止内本地转 Markdown;动态、资源页和嵌入文档使用一次有界 DOM eval;直接 PDF、DOCX 与媒体文件在调用前选择不带 --dump 和页面等待参数的原始响应路线。直接文档正文在同一截止预算内解析,文件任务按统一字节规则验收;不在失败后切换格式或借用其他 provider。": "Ordinary body tasks use Markdown. Challenge tasks use one JSON request to return the final DOM, HTTP status, final URL, and bounded network diagnostics, then convert that DOM to Markdown locally within the same deadline. Dynamic, resource-page, and embedded-document tasks use one bounded DOM eval. Direct PDF, DOCX, and media inputs preselect a raw-response route without --dump or page-wait flags. Direct-document text is parsed within the same deadline, and file tasks use shared byte-level validation; no post-failure format switch or cross-provider fallback is used.",
-    "使用官方高级提取路线;HTTP 200 但没有 raw_content 仍按适配器请求失败记录。原始响应在私有证据边界独立哈希,公开报告只保留规范化结果与绑定摘要。": "The run uses the documented advanced extraction route. An HTTP 200 response without raw_content is still recorded as an adapter request failure. The raw response is independently hash-bound as private evidence; the public report retains only the normalized result and binding summary.",
-    "没有正文或逐网址状态失败,不能仅凭 HTTP 200 判成功;原始 API 响应只进入私有哈希绑定证据,不随公开报告发布。": "Missing body text or a per-URL failure status cannot pass solely because the HTTP response is 200. Raw API responses remain private hash-bound evidence and are not published with the report.",
-    "浏览器 + 等待参数": "Browser plus wait parameters",
-    "裸 fetch 取得原始 PDF 后在预算内解析;Office 格式取决于实际响应": "A bare fetch retrieves the original PDF and parses it within the budget; Office support depends on the actual response",
-    "JS 渲染": "JavaScript rendering",
-    "输入范围": "Input coverage",
-    "结构化输出": "Structured output",
-    "访问与会话": "Access and sessions",
-    "普通网页输出 Markdown;直接文档或媒体输出序列化 HTML 或原始字节。": "Ordinary pages produce Markdown; direct documents or media produce serialized HTML or original bytes.",
-    "模型生成的摘要、OCR 文字、网页截图、资源 URL 与原始字节是不同产物;均不能自动当作独立标准答案。原文件实测仍需核验 MIME、字节数和 SHA-256。访问被拒绝只说明该次访问受限,不等于网站无效或所有 provider 均不支持。": "Model-generated summaries, OCR text, page screenshots, resource URLs, and original bytes are different deliverables; none automatically becomes an independent reference answer. Original-file measurements still validate MIME type, byte count, and SHA-256. A denied request shows only that this access attempt was restricted; it does not mean the site is invalid or every provider is unsupported.",
-    "接口边界与依据": "Interface boundaries and evidence",
-    "点击方法名定位;展开查看本轮启用了什么,以及哪些属于其他接口或配置。": "Select a method name to jump to its profile; expand it to see what was enabled in this run and what belongs to other interfaces or configurations.",
-    "普通网页原生 Markdown;直接文档与媒体使用裸 fetch 自动响应;原始文档的正文解析计入任务耗时": "Native Markdown for ordinary pages; automatic bare-fetch responses for direct documents and media; body parsing from original documents is included in task latency",
-    "moli fetch;普通正文使用 Markdown,动态/资源/嵌入文档使用有界 DOM eval,直接文档与媒体使用裸 fetch 自动响应": "moli fetch; ordinary body tasks use Markdown, dynamic/resource/embedded-document tasks use bounded DOM eval, and direct documents and media use automatic bare-fetch responses",
-    "Moli 1.1.4 · 输出与接口边界 ↗": "Moli 1.1.4 · output and interface boundaries ↗",
-    "Moli 1.1.4 · 官方发布 ↗": "Moli 1.1.4 · official release ↗",
-    "本轮 advanced + markdown + include_images=true,不传 query;未测试 Research 文件上传。": "This run uses advanced + markdown + include_images=true without query; Research file upload is not tested.",
-    "text.verbosity=full、maxAgeHours=0,并取 links/imageLinks;本轮没有调用摘要或高亮生成。": "This run uses text.verbosity=full and maxAgeHours=0 and collects links/imageLinks; summary and highlight generation are not called.",
-    "只调用 /markdown;普通网页使用 networkidle2,动态任务使用预声明 selector 和有界稳定等待;未提供源站登录 Cookie,未调用其他端点。": "Only /markdown is called. Ordinary pages use networkidle2, while dynamic tasks use a predeclared selector and bounded settling wait. No origin-login cookie is supplied and no other endpoint is called.",
-    "下载能力与来源数据": "Acquisition capability and source data",
-    "Beta 期免费;账户级限制": "Free during beta; account-level limits apply",
-    "2026-09-07 核验的 Kitesurf 专项文档明确写明 Beta 期免费且受每账户限制。通用 Browser Run 另有浏览器时长计价;在专项文档改口前,不把通用超额价格当成 Kitesurf Beta 的已证实单价。": "Kitesurf-specific documentation checked on 2026-09-07 explicitly states that the beta is free and subject to per-account limits. General Browser Run separately charges for browser duration; until the Kitesurf documentation changes, general overage pricing is not presented as a verified Kitesurf beta unit price.",
-    "官方 Kitesurf 文档 ↗": "Official Kitesurf documentation ↗",
-    "Browser Run 通用定价 ↗": "General Browser Run pricing ↗",
-    "本轮本地浏览器;无托管调用单价": "Local browser in this run; no managed-call unit price",
-    "核验于 2026-09-08。算力、网络、存储、代理和维护成本自担;没有测量这些成本,不把本地调用等同于零成本。官方托管产品或服务价格不属于本轮执行模式。": "Checked on 2026-09-08. Compute, network, storage, proxy, and maintenance costs are borne locally; they were not measured, so a local call is not treated as zero-cost. Pricing for official managed products or services is outside this run's execution mode.",
-    "2026-09-02 核验的公开源码候选集,包括搜索结果与登记文件。计数是已核验下限,不是全网采用量;点击数字查看仓库与文件。": "Public-source candidate set checked on 2026-09-02, including search results and registered files. Counts are verified lower bounds, not worldwide adoption totals; select a number to inspect repositories and files.",
-    "样本内出现该方法签名的去重仓库数;同一仓库只计一次。": "Distinct repositories in the sample containing the method signature; each repository is counted once.",
-    "样本内命中的去重仓库与文件组合数;不是调用次数。": "Distinct repository-file pairs matched in the sample; this is not a call count.",
-    "“—”表示未进行可归属的专用采用统计;0 表示本样本未命中。": "“—” means no attributable method-specific adoption count was performed; 0 means no match in this sample.",
-    "。源码已核验;该官方仓库不符合当前星数 / 活跃度筛选条件,单列来源,不计入采用样本。": ". The source was verified; this official repository does not satisfy the current stars / activity filter, so it is listed separately and excluded from the adoption sample.",
-})
 
 
 # Remaining interface copy is report prose rather than measured
 # provider output, so the English top-level pages localize it.
-TEXT.update({
-    "支持 · Browser Run 浏览器 + 等待参数": "Supported · Browser Run browser + wait parameters",
-
-})
 
 # Reader-facing task-family copy from report_components.py. These strings are
 # rendered on the methodology page and therefore need full English editions;
 # measured task names and prompts remain unchanged.
-TEXT.update({
-    "从网页中找到指定图片、视频资源或视频入口地址,或读取视频页登记的文字信息。这里测的是网页取数,不是图片或视频理解。": "Find the specified image, video, or video-entry URLs on a webpage, or read its registered video information. These tasks measure webpage data retrieval; image and video understanding are outside their scope.",
-    "请求媒体网页": "Request the media webpage",
-    "定位指定资源或信息": "Locate the required resource or information",
-    "提取地址或文字": "Extract URLs or text",
-    "交付题目要求的结果": "Return the required result",
-    "含图页面须返回登记的图片地址;视频信息页须返回标题与发布者。拿到地址不代表已经取得对应文件。": "Image pages must return the registered image URLs; video-information pages must return the title and publisher. Returning a URL does not establish that its file was obtained.",
-    "地址题只核对目标地址,文字题只核对登记信息;不检查画面、字幕、声音,也不要求下载媒体字节。": "URL tasks check the target URLs and text tasks check the registered information. They do not assess frames, captions, or audio, and do not require downloading media bytes.",
-    "取得直接媒体文件。必须返回可验证的图片或视频字节;复述网址、返回文件名或网页文字都不能代替文件。": "Retrieve a direct media file and return verifiable image or video bytes. Repeating a URL, filename, or webpage text cannot substitute for the file.",
-    "请求直接文件地址": "Request the direct file URL",
-    "接收响应字节": "Receive response bytes",
-    "识别文件格式": "Identify the file format",
-    "核对长度与 SHA-256": "Verify length and SHA-256",
-    "输入直接指向图片或视频文件,评测对象是返回的原始字节,不进行 Markdown 转换。": "The input points directly to an image or video file. Evaluation uses the returned original bytes without Markdown conversion.",
-    "直接 PNG 地址须交付与独立保存样本一致的文件格式、长度和 SHA-256;只返回该 PNG 的网址不通过。": "A direct PNG task must deliver a file matching the independently saved sample's format, length, and SHA-256. Returning only the PNG URL fails.",
-    "这组只验证文件交付,不测试图片描述、视频理解、语音转写或字幕质量。": "These tasks assess file delivery only. Image descriptions, video understanding, speech transcription, and caption quality are outside their scope.",
-    "输入网页,从页面里找到指定图片、视频或视频入口地址;另有 1 题读取视频页标题与发布者。": "Given a web page, find the specified image, video, or playback URL. 1 additional task asks for the video-page title and publisher.",
-    "地址题只核对返回值里是否出现目标地址,不要求另行下载媒体文件;文字题按登记事实核对。": "URL tasks check whether the returned value contains the target URL and do not require a separate media download. The text task checks the registered facts.",
-    "完成该题声明的地址或文字交付。拿到地址不等于取得文件,也不表示理解了图片、画面、字幕或声音。": "Return the URL or text required by the task. A URL alone does not deliver the file or show understanding of images, video, captions, or audio.",
-    "输入 1 个直接图片地址或 2 个直接视频地址,取得原始文件。": "Retrieve the original file from 1 direct image URL or either of 2 direct video URLs.",
-    "独立保存的原文件格式、长度和 SHA-256。": "Independently saved file format, length, and SHA-256.",
-    "核对实际返回字节的格式特征、长度和目标 SHA-256;不对二进制文件做 Markdown 转换。": "Check the returned bytes against the required file signature, length, and SHA-256. Binary files are not converted to Markdown.",
-    "必须实际交付匹配的文件字节;复述输入网址或只返回网页文字不通过。本组不测试媒体理解。": "The method must return the matching file bytes. Repeating the input URL or returning page text does not pass. These tasks do not test media understanding.",
-    "查看网页媒体参考与结果 →": "View web-media references and results →",
-    "查看原始文件参考与结果 →": "View original-file references and results →",
-    "完成该题声明的地址或文字交付。拿到地址不等于取得文件,也不表示理解了图片、画面、字幕或声音。查看网页媒体参考与结果 →": "Return the URL or text required by the task. A URL alone does not deliver the file or show understanding of images, video, captions, or audio. View web-media references and results →",
-    "必须实际交付匹配的文件字节;复述输入网址或只返回网页文字不通过。本组不测试媒体理解。查看原始文件参考与结果 →": "The method must return the matching file bytes. Repeating the input URL or returning page text does not pass. These tasks do not test media understanding. View original-file references and results →",
-    "本轮 100 例统一使用 Moli 原生 Markdown 和默认 done 完成方式。除目标 URL 外,没有逐例等待元素、额外等待时间、提取脚本或特殊参数,也没有启用理解模型、格式回退或付费后备方法。": "All 100 cases use Moli's native Markdown and default done completion mode. Beyond the target URL, there are no case-specific wait elements, added delays, extraction scripts, or special parameters. No interpretation model, format fallback, or paid backup method is enabled.",
-    "所有方法进入同一个评分入口,但按任务交付物验收,而不是强制使用同一种中间格式。网页与文档题验收可读正文;网页媒体题验收指定资源地址或登记文字;直接媒体文件题验收图片或视频字节;结构任务验收字段和稳定文字。HTML/Markdown 链接与明确配对的结构化名称—链接字段按同一规则核验;不把请求回显或诊断日志当成答案。原始输出与原始适配器状态保留,报告层不按方法名称改写任务结果。": "Every method enters the same scoring pipeline, while acceptance follows each task's required deliverable. Web and document tasks require readable body text; web-media tasks require the specified resource URL or registered text; direct-media tasks require image or video bytes; structure tasks require the declared fields and stable text. HTML and Markdown links follow the same checks as explicitly paired structured name–URL fields. Request echoes and diagnostic logs do not count as answers. Raw output and adapter status are preserved, and the report layer does not alter results by method name.",
-    "这个边界比较的是“拿到本任务最终交付物”所需时间:服务直接返回成品时计完整往返;本地方法若还需下载、渲染或解析,也必须完成后才停表。文档正文必须由参评方法交付为可读文字;直接媒体文件任务到字节校验完成即停止,不做无关的 Markdown 转换。所有方法使用同一外层计时器和截止预算。超时答案不接收;无法取消的调用会被隔离,整批结果因此不完整并触发不可靠状态,构建产物不得发布。": "This boundary measures the time needed to obtain the task's final deliverable. A service that returns a finished result includes the full round trip; a local method stops only after any required download, rendering, or parsing. The evaluated method must deliver document body text in readable form. Direct-media tasks stop after byte validation and do not perform an unrelated Markdown conversion. Every method uses the same outer timer and deadline. Late answers are rejected. Calls that cannot be cancelled are isolated, which leaves the batch incomplete and blocks publication.",
-    "完整正文任务除原有核对点外,按参考正文最多五个连续位置检查字符六元组覆盖:每个位置至少达到 60%,通常要求至少四处达到 80%。所有位置一次性做全局容量分配;同一返回片段实例只能计入一个位置,位置排列顺序不影响结论。若页面存在经独立证据确认的合法重排,案例可显式登记不同位置门槛。比较忽略空白和排版符号但保留文字顺序;全文 F1 独立保留实际值。直接媒体文件任务必须有字节验收,资源 URL 不等于文件。": "Alongside the existing checks, complete-body tasks measure six-character-shingle coverage across up to five consecutive reference regions. Every region must reach 60%, and normally at least four must reach 80%. A global capacity assignment ensures that each returned fragment instance counts toward only one region, regardless of region order. A task may declare different regional thresholds when independent evidence establishes legitimate reordering. Matching ignores whitespace and presentation marks while preserving text order; full-text F1 remains a separate measurement. Direct-media tasks require byte validation, and a resource URL does not count as a file.",
-})
-
-TEXT.update({
-    "langchain-webbase 官方实现:": "Official langchain-webbase implementation:",
-    "按本轮适配器介绍,不混用完整产品能力。输入示例展示正文配置,资源配置差异单列;它们不是每条历史请求的逐字回放。各调用所属批次及实际配置见“环境与复现”。输出示例取存档调用,经适配器整理后的真实节选,不冒充原始 HTTP 响应。Key 和账号均用占位符。": "Descriptions follow the adapters used in this run and do not imply each product's full capabilities. Request examples show body-text settings; resource-setting differences are listed separately. They are not verbatim replays of every historical request. See Reproduction for each call's batch and effective configuration. Response examples are genuine excerpts from archived calls after adapter normalization, not raw HTTP responses. Keys and account values use placeholders.",
-    "账号版 Browser Run 原生 Markdown 路径。客户端耗时包含公网往返、远端排队与执行;browser_ms_used 仅作附加诊断。": "Account-level Browser Run native-Markdown path. Client latency includes public-network round trips, remote queueing, and execution; browser_ms_used is an additional diagnostic only.",
-    "普通网页正文": "Ordinary web-page body",
-    "直接取得 Markdown": "Markdown returned directly",
-    "直接文档或媒体": "Direct document or media input",
-    "由 CLI 自动选择原始响应或序列化页面;不附加页面等待参数": "The CLI automatically selects a raw response or serialized page; no page-wait parameters are added",
-    "普通网页为 Markdown;直接文件为原始字节": "Markdown for ordinary pages; raw bytes for direct files",
-    "直接文档正文使用的本地解析器": "Local parser used for direct-document body text",
-    "本方法只使用官方 1.0.6 发行二进制,与其他 Moli 版本分开计分。普通网页使用原生 Markdown;直接文档与媒体的裸 fetch 路线在正式二进制上实测确认。正文任务若取得受支持的原始文档,会在同一计时预算内解析文字;文件任务直接验收字节。不在失败后切换引擎或输出模式。": "This method uses only the official 1.0.6 release binary and is scored separately from other Moli versions. Ordinary pages use native Markdown. Bare fetch for direct documents and media was verified on the release binary. When a body task obtains a supported original document, text is parsed within the same timing budget; file tasks validate bytes directly. The engine or output mode is not changed after a failure.",
-    "本方法只使用官方 1.1.3 发行二进制,与其他 Moli 版本分开计分。普通网页使用原生 Markdown;直接文档与媒体的裸 fetch 路线在正式二进制上实测确认。正文任务若取得受支持的原始文档,会在同一计时预算内解析文字;文件任务直接验收字节。不在失败后切换引擎或输出模式。": "This method uses only the official 1.1.3 release binary and is scored separately from other Moli versions. Ordinary pages use native Markdown. Bare fetch for direct documents and media was verified on the release binary. When a body task obtains a supported original document, text is parsed within the same timing budget; file tasks validate bytes directly. The engine or output mode is not changed after a failure.",
-    "本轮 networkidle2,等待页面网络活动趋于空闲": "networkidle2 in this run; wait until page network activity settles",
-    "Markdown 正文 → content": "Markdown body → content",
-    "最终 URL、标题、源站状态等": "Final URL, title, origin status, and related fields",
-    "本轮是 Markdown 端点,不是截图或浏览器控制接口;账户配额不足会单列失败。": "This run uses the Markdown endpoint, not screenshot or browser-control APIs. Insufficient account quota is reported as a distinct failure.",
-    "· 未纳入本轮;参数说明为仓库登记模式,不计入本轮结果 ·": "· Not included in this benchmark; parameters describe the repository-registered mode and do not contribute to results ·",
-    "正文使用本地无头 Chrome;文件任务在调用前选择原生 AsyncHTTPCrawlerStrategy.crawl(url, config),读取其 downloaded_files 并统一验收字节。媒体链接不等于下载;文件字节校验不等于理解媒体。": "Body text uses local headless Chrome. File tasks select native AsyncHTTPCrawlerStrategy.crawl(url, config) before the call, read downloaded_files, and apply the shared byte-validation rules. A media link is not a download, and validating file bytes is not media understanding.",
-    "文本块合并 → content": "Merged text blocks → content",
-    "成功态的规范源站状态与最终 URL": "Canonical origin status and final URL for a successful execution",
-    "展示投影;仅在规范值缺失时补充状态或 URL": "Presentation projection; used for status or URL only when the canonical value is absent",
-    "本轮 response 上限 10 MB、正文上限 200000 字符;不是 DeepSeek 聊天 API,也没有调用模型生成答案。": "This run caps each response at 10 MB and body text at 200000 characters. This is not the DeepSeek chat API, and no model generates the answer.",
-    "本轮 true,要求正文": "true in this run; body text required",
-    "额外链接和图片": "Additional links and images",
-    "没有正文或逐网址状态失败,不能仅凭 HTTP 200 判成功。": "An HTTP 200 alone does not establish success when body text is absent or a per-URL status failed.",
-    "正文/资源为 markdown、links、images;原文件唯一使用 rawBase64": "markdown, links, and images for body/resource tasks; rawBase64 alone for original-file tasks",
-    "正文配置为 markdown;资源配置为 markdown、links、images": "markdown for body tasks; markdown, links, and images for resource tasks",
-    "资源 → provider_metadata": "Resources → provider_metadata",
-    "错误或额度原因": "Error or quota reason",
-    "公开 HTTP(S) 页面": "Public HTTP(S) page",
-    "done;可配置 networkidle / domstable 等": "done; networkidle / domstable and other states are configurable",
-    "毫秒;与外层进程截止时间使用同一预算": "Milliseconds; shares the outer process deadline budget",
-    "默认启用,阻止私网地址": "Enabled by default to block private-network addresses",
-    "原样保存 Markdown → content": "Markdown retained verbatim → content",
-    "0 且正文非空表示工具执行成功,任务另按规则判定": "0 with nonempty body text means tool execution succeeded; task acceptance is judged separately",
-    "未提供": "Not provided",
-    "Markdown 模式没有这些字段,不伪造 HTTP 200 或最终 URL": "Markdown mode does not expose these fields; HTTP 200 and a final URL are not fabricated",
-    "正文用 Markdown;直接文件任务预先选择不带 --dump 和页面等待参数的自动响应,保存原始字节后按统一文件规则验收。不是失败后的切换或额外下载。七方法报告中的直接 PDF/DOCX 正文另由 headless 适配器在同一截止预算内解析。": "Body output uses Markdown. Direct-file tasks preselect automatic responses without --dump or page-wait parameters, retain raw bytes, and apply the shared file rules. This is neither a post-failure switch nor an extra download. For direct PDF/DOCX body tasks in the seven-method report, the headless adapter parses within the same deadline budget.",
-    "本轮 basic": "basic in this run",
-    "HTTP 200 但没有 raw_content 仍按适配器请求失败记录;原始状态与规范化依据分开保留。": "An HTTP 200 without raw_content is still recorded as an adapter request failure. Raw status and normalization evidence are retained separately.",
-    "2026-09-02 核验:15 个签名,纳入 87 个仓库。候选集包括每签名前 100 条搜索结果和已登记的专用签名文件;按默认分支逐文件核验。要求 stars ≥ 1,000、近 7 天活跃且未归档。": "Checked on 2026-09-02: 15 signatures across 87 included repositories. The candidate set contains the first 100 search results per signature plus registered signature-specific files, each checked on the default branch. Repositories require stars ≥ 1,000, activity within 7 days, and non-archived status.",
-    "同一方法在同一仓库命中多处,仓库数只加 1;同一文件命中多个签名,文件数只加 1。保留样本中的官方仓库、示例、文档和 fork,不冒充独立生产用户。": "Multiple matches for one method in one repository count as 1 repository; multiple signatures in one file count as 1 file. Official repositories, examples, documentation, and forks remain in the sample and are not presented as independent production users.",
-    "搜索结果有截断和排序偏差,计数是已核验候选集的下限,不是全网热度排名。0 只表示候选集中未发现符合条件的仓库;官方实现与第三方引用不同,示例、文档及源码命中也不证明生产采用。": "Search truncation and ranking bias make these counts lower bounds for the reviewed candidate set, not an internet-wide popularity ranking. 0 means only that no qualifying repository was found in the candidate set. Official implementations differ from third-party references, and matches in examples, documentation, or source do not establish production adoption.",
-    "逐一读取命中的 Git 文件并校验内容。通用 web_fetch 和同名函数不能证明归属;Exa 要求 Contents 端点或 SDK 导入及 get_contents。内置方法也包括源码复制与官方仓库,不能冒充第三方集成。": "Every matched Git file is read and its content verified. Generic web_fetch names or same-named functions do not establish attribution. Exa requires the Contents endpoint or an SDK import plus get_contents. Built-in-method counts also include copied source and official repositories, which are not presented as third-party integrations.",
-    "固定十二方法升级合同与重跑范围": "Freeze the twelve-method refresh contract and rerun scope",
-    "十二方法报告已重新绑定当前评测集、实现身份、选择性重跑计划和独立发布目标。主矩阵计划新增 432 次调用,同窗测速计划新增 240 次;这些调用及独立核验尚未在本条说明中宣告完成,因此不公布新分数、排名或版本结论。": "The twelve-method report is now bound to the current dataset, implementation identities, selective-rerun plan, and a separate publication target. The primary matrix plans 432 new calls and the same-window latency cohort plans 240. This entry does not claim that those calls or their independent review are complete, so it reports no new scores, rankings, or version conclusions.",
-    "受路线影响的文档结果进入声明的重跑范围;保存的新输出完成评分和盲审前,不把实现修正写成能力提升。": "Document results affected by this path are included in the declared rerun scope. The implementation fix is not presented as a capability gain before the new saved outputs have been scored and blindly reviewed.",
-    "主矩阵预先声明 13 题 × 12 方法、LangChain 与 Crawl4AI 的其余完整方法 cohort,以及两个超时边界修正,共 432 次新调用;另以 provider-blind、域名去重的 20 题执行 240 次十二方法同窗测速。": "The primary matrix predeclares 13 tasks × 12 methods, the remaining full method cohorts for LangChain and Crawl4AI, and two deadline-boundary corrections, for 432 new calls in total. A separate provider-blind, hostname-deduplicated set of 20 tasks produces 240 same-window latency calls across the twelve methods.",
-    "主矩阵仍固定每个方法—任务一个权威逻辑结果,不按最好或最快挑选;测速只在共同成功样本上比较。合计 672 次是计划调用数,不是已完成数。": "The primary matrix still fixes one authoritative logical result per method–task pair and never selects the best or fastest response. Latency is compared only on jointly successful tasks. The combined 672 calls are planned, not completed.",
-    "这些是报告正确性、可读性和发布隔离修正,不改变任何已保存 provider 输出或既有分数;只有完成计划重跑、全量独立核验和远端逐文件验证后,才可发布新成绩。": "These fixes improve report correctness, readability, and publication isolation without changing any saved provider output or existing score. New results may be published only after the planned reruns, complete independent review, and remote per-file verification are finished.",
-})
+
 
 
 # Some generated blocks concatenate several fixed sentences into one text node,
 # or split a sentence around an inline link/code element.  These replacements
 # are deliberately narrow report phrases; they never include task names,
 # reference answers, or provider output.
-PHRASES = {
-    "处理路线:": "Retrieval path: ",
-    "的 Moli 已保存输出仍包含": "'s saved Moli output still contains ",
-    "Quotes to Scrape 的": "Quotes to Scrape's ",
-    "CowAgent 已通过网页中 F1 最低的是": "The lowest-F1 general-web task passed by CowAgent is ",
-    "CowAgent 的已保存输出": "CowAgent's saved output ",
-    "在这份输出的内存副本中,把": "In an in-memory copy of this output, replace ",
-    "保留完整协议,": "retains the complete protocol; ",
-    "同一个 Books to Scrape 站点,CowAgent 的": "On the same Books to Scrape site, CowAgent's ",
-    "书目正文任务": "catalog body-text task",
-    "商品封面地址任务": "product-cover URL task",
-    "未通过。书名和价格留在文本里,不能保证封面地址也交付了。下游需要分别验收文字和资源地址,才能发现这种交付差异。": " fails. Titles and prices remaining in text do not guarantee delivery of cover URLs. Downstream must validate text and resource URLs separately to detect this difference.",
-    "没有任何方法满足任务规则的案例包括:": "Tasks passed by no method include: ",
-    "。这些共同缺口使单纯增加后备方法的收益存疑;应先查实际输出缺少哪些字段、是否只有加载占位,或是否收到拦截页面。登录、地区和访问限制本身不使任务无效,它们可能就是目标业务需要处理的条件。": ". These shared gaps make the value of simply adding a fallback uncertain. First inspect missing fields, loading placeholders, or block pages. Login, region, and access constraints do not invalidate the task; they may be conditions the target workflow must handle.",
-    "Chromium 渲染 DOM → node-html-markdown 2.0.0": "Chromium rendered DOM → node-html-markdown 2.0.0",
-    "未启用额外的理解模型或付费后备方法。": "No additional understanding model or paid fallback was enabled.",
-    "调用参数与交付路线以本次配置和逐例记录为准。": "See the run configuration and individual records for call parameters and delivery paths.",
-    "交付路线(": "Delivery paths (",
-    "输出格式(": "Output formats (",
-    " 条记录):": " records): ",
-    "未记录": "Not recorded",
-    "正文与资源题请求 Markdown、links、images;原文件题改用唯一 rawBase64 格式并验收解码字节。动态顶层选择器使用 wait action,数量与 iframe 条件明确标为未完整执行。": "Body and resource tasks request Markdown, links, and images; original-file tasks use rawBase64 as the sole format and validate the decoded bytes. Dynamic top-level selectors use a wait action, while count and iframe conditions are explicitly marked as not fully enforced.",
-    "本轮请求 Markdown;图片资源仅来自同一 Extract 响应的 images 字段。": "This run requests Markdown; image resources come only from the images field in the same Extract response.",
-    "本轮使用 Advanced Extract。套餐表按 Basic 满额换算;本轮 Advanced 用量相同则积分翻倍、每千页价格翻倍,不能把搜索的一次调用价格套给 Extract。": "This run uses Advanced Extract. Plan prices are normalized at full Basic utilization; Advanced uses twice the credits for the same number of successful URLs and therefore doubles the per-thousand-page price. Search call pricing does not apply to Extract.",
-    "Advanced(本轮)": "Advanced (this run)",
-    "本轮请求正文文本;资源链接仅来自同一 Contents 响应的 extras 字段。": "This run requests body text; resource links come only from the extras field in the same Contents response.",
-    "当前成绩包含对保存输出的离线重算;收据记录输入、输出哈希、判定变化及供应商调用数,不把新规则伪装成原调用时的运行配置。": "Current scores include offline rescoring of saved outputs. Receipts record input and output hashes, verdict changes, and provider-call counts without presenting new rules as the original run configuration.",
-    "本轮时延纠正保留旧值与全部物理调用,并按预先固定的最终轮更新权威逻辑结果,不能把跨批次视图当成同时重跑。": "This run's latency correction retains old values and every physical call, and updates authoritative logical results from the preselected final round; the cross-batch view is not a simultaneous rerun.",
-    "方法配置、凭据变量和本轮实际版本分别见“方法介绍”“账号”和上方方法版本表;不存在的 headless 专用配置不属于本 cohort。": "See Methods, Access, and the version table above for method configuration, credential variable names, and measured versions. Headless-only configuration that does not exist for this cohort is out of scope.",
-    ";不得返回安全验证或拒绝访问页": "; must not return a security-verification or access-denied page",
-    "高。核验时隐藏机器结论和分数,只提供任务契约、标准答案与完整实际输出;分歧回到原始证据裁定,未决非零则禁止发布。任务和输出未变化时沿用原核验;发生变化的调用必须重新盲审。": "high. Machine verdicts and scores are hidden during review; only the task contract, reference answer, and complete actual output are provided. Disagreements are adjudicated from original evidence, and any unresolved item blocks publication. Existing reviews are retained when task and output are unchanged; changed calls require a new blind review.",
-    "Codex 独立强推理代理": "Codex independent high-reasoning agent",
-    "任务运行时默认模型": "the task runtime's default model",
-    ";推理强度:高。": "; reasoning effort: high.",
-    "关键事实按规范化后的可见文本机械匹配:统一 Unicode、大小写及排版空白,菜单路径如 Settings": "Key facts are matched mechanically against normalized visible text: Unicode, letter case, and layout whitespace are normalized, so menu paths such as Settings",
-    "AI 与 Settings": "AI and Settings",
-    "AI 等价;忽略 Markdown 链接地址、行内代码与加粗标记。一个已被完整记录边界约束的字段允许丢失内联元素间空白,例如标签列表": "AI are equivalent; Markdown link destinations, inline-code markers, and emphasis markers are ignored. A field whose complete record boundary is known may omit whitespace between inline elements, such as the tag list",
-    "有参考时,请求失败计 0;请求成功但任务未通过仍按实际正文评分。": "With a complete reference, request failures score 0; successful requests that fail task acceptance are still scored using their actual body text.",
-    "文字核对覆盖只衡量各例登记的必需文字组,不等于全文完整度;没有文字核对依据的案例不进入该项均值。": "Required-text coverage measures only registered required-text groups; it is not full-text recall. Tasks without text evidence are excluded from that average.",
-    "判分完整性是已有明确判定的调用数/计划调用数,不是通过率;任何方法未满即表示整份报告不完整,禁止发布。": "Judgement coverage is judged calls divided by planned calls, not a pass rate. If any method is incomplete, the entire report is incomplete and publication is blocked.",
-    "均值、P50、P95 仅统计各方法自身通过项,不能直接视为同题速度排名。": "Mean, P50, and P95 include only each method's own passing calls and are not a matched-task speed ranking.",
-    "各类型按各自任务范围比较,不合并为跨类型总分。": "Each family is compared within its own task scope and is not combined into a cross-family weighted score.",
-    "含有已排除易变字段的旧快照文字分仅保留在调用详情,不进入本表全文均值。": "Legacy snapshot-text scores containing excluded volatile fields remain in attempt details and are excluded from this table's full-text averages.",
-    "列表、固定字段标签与说明共同验收;标题文字质量仅作下方辅助诊断。": "Lists, stable field labels, and descriptions are judged together; title-text quality is only a supporting diagnostic below.",
-    "直接文件必须有字节验收才能判分;任何缺失都会使整份报告标为不可靠。资源、文件、信息三列分别按对应任务计算。": "Direct-file tasks require byte validation. Any missing evidence makes the report unreliable. Resource, file, and metadata columns are calculated on their corresponding task subsets.",
-    "每种方法都执行同一组 150 个任务,每题计一次通过或失败,直接相加;耗时单列展示,不参与通过率排序。总榜适合看整体交付覆盖,具体能力仍应回到六个分榜。": "Every method runs the same 150 tasks. Each task contributes one pass or fail and the results are summed directly; latency is shown separately and does not affect pass-rate ranking. The overall ranking shows delivery coverage, while specific capabilities belong in the six family rankings.",
-    "接口正常返回次数 ÷ 全部调用次数;不代表内容正确。": "Calls with a normal interface response divided by all calls; this does not establish content correctness.",
-    "满足本例全部验收规则的次数 ÷ 已测任务次数;已测任务的请求失败计入分母。未测排除且单列覆盖,不设全文 F1 门槛。": "Calls satisfying every task rule divided by measured tasks. Request failures remain in the denominator; unmeasured tasks are excluded and reported separately. There is no full-text F1 threshold for task acceptance.",
-    "每例命中的必需文字组数 ÷ 登记文字组数,再按案例取均值;请求失败计 0。实际覆盖范围在表前说明。事实句、标题或说明均按组匹配;难度不同,不宜跨榜单直接比较。不是全文完整度,空核对集不计为 100%。": "Required text groups matched divided by registered groups, averaged by task; request failures score 0. Coverage scope is stated before each table. Facts, headings, and descriptions are matched by group. Difficulty varies, so values should not be compared across rankings. This is not full-text recall, and an empty check set is not treated as 100%.",
-    "可判定通过或失败的调用数 ÷ 计划调用数。它是报告可靠性门槛,不是通过率或正文完整度;任何一行未满都禁止发布排名。": "Calls with a pass/fail judgement divided by planned calls. This is a report reliability gate, not pass rate or body completeness; any incomplete row blocks publication of rankings.",
-    "执行者:": "Reviewer: ",
-    ";模型:": "; model: ",
-    ";推理强度:": "; reasoning effort: ",
-    "。核验时隐藏机器结论和分数,只提供任务契约、标准答案与完整实际输出;分歧回到原始证据裁定,未决非零则禁止发布。任务和输出未变化时沿用原核验;发生变化的调用必须重新盲审。": ". Machine verdicts and scores are hidden during review; only the task contract, reference answer, and complete actual output are provided. Disagreements are adjudicated from original evidence, and any unresolved item blocks publication. Existing reviews are retained when task and output are unchanged; changed calls require a new blind review.",
-    "在仓库本地 .env 配置": "Configure",
-    ";报告和示例只保留占位符。": " in the repository-local .env; reports and examples retain placeholders only.",
-    "完整参考覆盖 98/100;缺少参考只影响全文指标,不影响任务结果。": "Complete-reference coverage: 98/100. A missing reference affects only full-text metrics, not task acceptance. ",
-    "完整参考覆盖 5/10;缺少参考只影响全文指标,不影响任务结果。": "Complete-reference coverage: 5/10. A missing reference affects only full-text metrics, not task acceptance. ",
-    "完整参考覆盖 0/10;缺少参考只影响全文指标,不影响任务结果。": "Complete-reference coverage: 0/10. A missing reference affects only full-text metrics, not task acceptance. ",
-    "完整参考覆盖 10/10;缺少参考只影响全文指标,不影响任务结果。": "Complete-reference coverage: 10/10. A missing reference affects only full-text metrics, not task acceptance. ",
-    "文字核对范围:各方法均为 100/100 例。": "Required-text coverage: 100/100 tasks for every method. ",
-    "文字核对范围:各方法均为 8/10 例。": "Required-text coverage: 8/10 tasks for every method. ",
-    "文字核对范围:各方法均为 9/10 例。": "Required-text coverage: 9/10 tasks for every method. ",
-    "文字核对范围:各方法均为 10/10 例。": "Required-text coverage: 10/10 tasks for every method. ",
-    "在评测仓库中执行;读取 evaluation/ 的当前实测数据,不调用供应商接口。": "Run in the benchmark repository using the current measured data in evaluation/; this does not call provider APIs.",
-    "输出目录:reports/webfetch/。固定的 Python 3.11 与 uv.lock 依赖不满足时会立即失败。凭据仅从本地环境加载,不包含在报告中。": "Output directory: reports/webfetch/. The command fails immediately unless the pinned Python 3.11 and uv.lock dependencies are satisfied. Credentials are loaded only from the local environment and are not included in the report.",
-    "完成十二方法升级、影响范围重跑与失败项核验": "Complete the twelve-method upgrade, impact-scoped reruns, and failure review",
-    "十二方法报告已重新绑定当前评测集、实现身份和独立发布目标。主矩阵按预声明影响范围新增 432 次调用,同窗测速新增 240 次;主矩阵 705 条机器失败结果完成独立核验,1,095 条机器通过结果未逐条审查,分歧逐条裁决,未决为 0。": "The twelve-method report is now bound to the current evaluation set, implementation identities, and an independent publication target. The primary matrix adds 432 calls within the predeclared impact scope and the same-window latency cohort adds 240 calls. All 705 machine failures in the primary matrix received independent review; the 1,095 machine passes were not reviewed individually. Every disagreement was adjudicated, with 0 unresolved.",
-    "受路线影响的文档结果已进入声明的重跑范围;评分与盲审均使用新保存输出。": "Document results affected by the route change were included in the declared rerun scope; scoring and blind review both use the newly saved outputs.",
-    "主矩阵预先声明 13 题 × 12 方法、LangChain 与 Crawl4AI 的其余完整方法 cohort,以及两个超时边界修正,共完成 432 次新调用;另以 provider-blind、域名去重的 20 题完成 240 次十二方法同窗测速。": "The primary matrix predeclared 13 tasks × 12 methods, the remaining full-method cohorts for LangChain and Crawl4AI, and two deadline-boundary corrections, for 432 new calls in total. A separate provider-blind, domain-deduplicated set of 20 tasks produced 240 same-window latency calls across all twelve methods.",
-    "主矩阵固定每个方法—任务一个权威逻辑结果,不按最好或最快挑选。测速保留完整预声明矩阵:全调用延迟按同一 20 题分母统计,成功调用延迟另作条件统计;十二方法共同通过为 1/20,只作为诊断,不据此筛样本或排名。": "The primary matrix fixes one authoritative logical result for every method–task pair and never selects the best or fastest result. The latency cohort retains its complete predeclared matrix: all-call latency uses the same 20-task denominator, while successful-call latency is reported separately as a conditional statistic. All twelve methods jointly passed 1/20 tasks; this is diagnostic only and is not used to select samples or rank methods.",
-    "证据与失败项核验": "Evidence and failure review",
-    "旧组装器可能把 worker 工作树和符号链接带入汇总,又会漏掉重试物理调用返回的辅助文件;审查包若遗漏原生答案字段,也会制造系统性假分歧。": "The old assembler could pull worker trees and symbolic links into the aggregate while omitting auxiliary files returned by physical retry calls. A review packet that omitted native answer fields could also create systematic false disagreements.",
-    "汇总只复制哈希绑定的公开文件,拒绝符号链接和工作树路径,并绑定主返回、辅助文件与每次物理重试。Codex gpt-5.6-sol 对主矩阵 705 条机器失败结果盲审,审查包同时提供白名单化的原生答案 metadata;2 条分歧逐条裁决。": "Aggregation now copies only hash-bound public files, rejects symbolic links and worker-tree paths, and binds the primary response, auxiliary files, and every physical retry. Codex gpt-5.6-sol blindly reviewed the 705 machine failures in the primary matrix, with allowlisted native answer metadata included in the review packet; the 2 disagreements were adjudicated individually.",
-    "最终证据为 12 × 150 的唯一主矩阵和 12 × 20 的唯一测速矩阵;主矩阵失败项核验 705/705,机器通过未逐条审查 1,095 条,测速结果不在本次审查范围,未决 0。裁决只确定审计结论,不回写或择优替换 provider 输出。": "The final evidence consists of one 12 × 150 primary matrix and one 12 × 20 latency matrix. Failure review covers 705/705 primary-matrix failures; 1,095 machine passes were not reviewed individually, and latency results were outside this review scope. There are 0 unresolved items. Adjudication determines only the audit conclusion and never rewrites or selectively replaces provider output.",
-    "发布器逐文件核验公开文件;服务端允许的 HTML 外壳注入与匿名字节一致性分层披露,不再把两者混称为全站字节相同。": "The publisher verifies public files individually. Server-permitted HTML shell injection and anonymous byte equality are disclosed as separate layers rather than being conflated as site-wide byte identity.",
-    "环境导入": "Environment import",
-    "托管方法凭据已存在于用户级 ~/.env,但旧流程只读取仓库 .env,导致新会话会误报缺少变量。": "Credentials for hosted methods already existed in the user-level ~/.env, but the old workflow read only the repository .env, causing new sessions to report variables as missing incorrectly.",
-    "新增显式 import-env:仅将九个允许变量从用户级文件补入仓库 .env,保留已有非空值、隐藏变量内容并强制 0600 权限;文档和测试固定该入口。": "An explicit import-env command now copies only nine allowlisted variables from the user-level file into the repository .env, preserves existing non-empty values, hides variable contents, and enforces 0600 permissions. Documentation and tests make this the stable entry point.",
-    "凭据值仍不进入 Git、证据、日志或报告;导入后六个托管方法的前置校验通过。": "Credential values still never enter Git, evidence, logs, or the report. Preflight validation passes for all six hosted methods after import.",
-    "账号 · Web Fetch": "Access · Web Fetch",
-    "账号与凭据": "Accounts and credentials",
-    "只列官方入口和环境变量名称,不保存实际凭据。": "Only official entry points and environment-variable names are listed; actual credentials are never stored.",
-    "注册清单": "Setup checklist",
-    "提供方": "Provider",
-    "官方入口": "Official entry point",
-    "环境变量": "Environment variables",
-    "取得方式": "How to obtain",
-    "免费 / 准入说明": "Free tier / access notes",
-    "注册 Cloudflare,复制 32 位 Account ID,再创建自定义 API token:Account → Browser Rendering → Edit;运行前用 GET /client/v4/user/tokens/verify 验证。": "Register with Cloudflare, copy the 32-character Account ID, then create a custom API token with Account → Browser Rendering → Edit; verify it with GET /client/v4/user/tokens/verify before running.",
-    "Workers Free 可用 Browser Rendering;token 需要 Browser Rendering Edit 权限,并受账户限额约束。": "Browser Rendering is available on Workers Free; the token needs Browser Rendering Edit permission and remains subject to account limits.",
-    "检出并固定 DeepSeek Harness 源码与依赖;本轮不调用聊天模型。": "Check out and pin the DeepSeek Harness source and dependencies; this run does not call a chat model.",
-    "注册或登录 Exa 控制台,打开 API Keys,创建 key;如系统要求则设置预算。": "Register for or sign in to the Exa console, open API Keys, and create a key; set a budget if required by the system.",
-    "需要控制台账号;用量由 credits/预算控制。": "A console account is required; usage is governed by credits and budget.",
-    "注册账号,打开 Firecrawl 控制台 API Keys 页面,创建以 fc- 开头的 key。": "Register an account, open the API Keys page in the Firecrawl console, and create a key beginning with fc-.",
-    "存在无 key 的限流层;可复现评测应使用免费账号。": "A rate-limited keyless tier exists; reproducible evaluation should use a free account.",
-    "登录中国区控制台,复制同一项目下的 API key 与 Project ID;本评测使用 api.lexmount.cn。": "Sign in to the China-region console and copy the API key and Project ID from the same project; this evaluation uses api.lexmount.cn.",
-    "API key 与 Project ID 必须来自同一个中国区项目。": "The API key and Project ID must come from the same China-region project.",
-    "登录后打开 Settings → API Keys,创建或复制 API key,并复制对应 Project ID;本评测使用 api.lexmount.com。": "After signing in, open Settings → API Keys, create or copy an API key, and copy the corresponding Project ID; this evaluation uses api.lexmount.com.",
-    "内测准入;API key 与 Project ID 必须来自同一个 Global 项目。": "Private-beta access; the API key and Project ID must come from the same Global project.",
-    "检出并固定 OpenClaw 源码及 Node 运行时;本轮关闭 Firecrawl 回退。": "Check out and pin the OpenClaw source and Node runtime; the Firecrawl fallback is disabled in this run.",
-    "注册或登录 Tavily Platform,从控制台复制 API key。": "Register for or sign in to Tavily Platform and copy an API key from the console.",
-    "官方快速开始说明每月有 1,000 免费 API credits,无需绑卡。": "The official quickstart states that 1,000 free API credits are available each month with no card required.",
-}
+PHRASES = dict(_TRANSLATIONS['PHRASES'])
 
 
 # The adoption page carries a large, evidence-bound capability and pricing
 # inventory. Keep its exact-string translations in small data-only modules so
 # the shared localization engine remains reviewable.
-for adoption_text in (
-    ADOPTION_TEXT_A,
-    ADOPTION_TEXT_B,
-    ADOPTION_TEXT_C,
-    ADOPTION_TEXT_D,
-):
-    TEXT.update(adoption_text)
 
 
 def _body_region_summary(match):
@@ -2541,207 +249,26 @@ def _body_region_summary(match):
 
 
 # Current standalone article: authored prose only.
-TEXT.update({
-    "Moli 的 Web Fetch 实测:": "Moli Web Fetch benchmark:",
-    "五款浏览器,150 道任务": "five browsers, 150 tasks",
-    "Chromium:125 / 150 · 83.3%": "Chromium: 125 / 150 · 83.3%",
-    "多通过 10 道,提高 6.7 个百分点": "10 more tasks passed; 6.7 percentage points higher",
-    "平均耗时 · 同样 8 道网页": "Mean latency · the same 8 pages",
-    "Chromium:5.28 秒": "Chromium: 5.28 seconds",
-    "Moli 平均少等 1.87 秒,减少 35.5%": "Moli takes 1.87 seconds less on average, a 35.5% reduction",
-    "P50 · 同样 8 道网页": "P50 · the same 8 pages",
-    "Chromium:3.63 秒,Moli 减少 25.8%": "Chromium: 3.63 seconds; Moli is 25.8% lower",
-    "P95:Moli 6.55 秒,Lightpanda 6.03 秒": "P95: Moli 6.55 seconds; Lightpanda 6.03 seconds",
-    "浏览器与 AI": "Browsers and AI",
-    "AI 读到的网页,取决于浏览器交回什么": "What AI reads depends on what the browser returns",
-    "给 AI 一个论文链接,并不等于把论文交给了它。程序需要访问网址、取得响应,再把正文提取出来。遇到动态网页,正文可能要等页面脚本执行后才出现;遇到 PDF,还要从文件里解析文字。这一段从 URL 到所需内容的工作,通常称为 Web Fetch。": "Giving AI a paper’s URL does not give it the paper itself. Software must visit the address, retrieve the response, and extract the text. On a dynamic page, the text may appear only after scripts run. A PDF requires text extraction from a file. This work of turning a URL into the requested content is commonly called Web Fetch.",
-    "Web Fetch:读取内容": "Web Fetch: retrieve content",
-    "输入论文地址,返回可供总结的正文。任务也可以要求商品列表、图片下载地址或原始文件。": "Enter a paper’s URL and retrieve text that can be summarized. A task may instead require a product list, an image download URL, or an original file.",
-    "正文或文件": "Text or file",
-    "Browser Use:操作网页": "Browser Use: interact with pages",
-    "让 AI 搜索商品、筛选规格,再加入购物车。它需要看当前页面,决定点击或填写什么,操作后再继续观察。": "Ask AI to search for a product, filter specifications, and add it to the cart. It must inspect the current page, decide what to click or fill in, act, and inspect the result.",
-    "文档与媒体:Moli 通过 24/30,Chromium 通过 14/30": "Documents and media: Moli passes 24/30, Chromium 14/30",
-    "把 150 道题按内容拆开,净增的 10 道全部来自文档、图片和视频。120 道网页任务中,两者各通过 111 道。": "Breaking the 150 tasks down by content, all 10 net additional passes come from documents, images, and videos. Of the 120 web tasks, both browsers pass 111.",
-    "其中六道 PDF 题只有 Moli 的本轮配置通过。Moli 先取得 PDF 文件,再交给本地 pypdf 解析器提取正文;其余四款使用本轮配置的 Markdown 输出路线,未能交回合格正文。对需要读论文的 AI 来说,这一步决定了后续有没有内容可读。成绩属于这条完整处理路径,PDF 解析时间也计入耗时。": "Six PDF tasks pass only with Moli’s tested configuration. Moli first retrieves the PDF, then uses local pypdf to extract text. The other four browsers’ tested Markdown output paths do not return acceptable text. For AI reading a paper, this step determines whether there is content to work with. The score belongs to this complete processing path, and latency includes PDF parsing.",
-    "网页类的得失则发生在不同站点。普通网页中,Moli 通过 97/100,Chromium 通过 99/100;热门站点中,Moli 为 6/10,Chromium 为 4/10。只看合计的 111 道,会漏掉这两种差异。": "Web successes and failures occur on different sites. Moli passes 97/100 general pages versus Chromium’s 99/100. On popular sites, Moli scores 6/10 versus Chromium’s 4/10. The shared total of 111 hides these two differences.",
-    "同样 8 道网页,平均耗时从 5.28 秒降到 3.41 秒": "The same 8 pages: mean latency falls from 5.28 to 3.41 seconds",
-    "速度比较使用预先选定的 20 道普通网页,其中 8 道被完整报告里的全部受测配置成功读取。只比较这 8 个相同网页,可以排除“很快返回空内容”对成绩的影响。Moli 的平均耗时为 3.41 秒,Chromium 为 5.28 秒,Lightpanda 为 3.60 秒。": "The latency comparison starts with 20 preselected general pages. 8 are successfully retrieved by every configuration in the full report. Comparing those same 8 pages prevents a quick empty response from improving a score. Mean latency is 3.41 seconds for Moli, 5.28 for Chromium, and 3.60 for Lightpanda.",
-    "平均耗时反映这组任务的总体等待;P50 是中位数,反映处于中间位置的任务耗时。Moli 的 P50 为 2.70 秒,Chromium 为 3.63 秒,减少 25.8%。均值与 P50 两项,Moli 都是这五款中最低。": "Mean latency describes the average wait across these tasks. P50 is the median, the middle value. Moli’s P50 is 2.70 seconds versus Chromium’s 3.63 seconds, a 25.8% reduction. Moli has the lowest mean and P50 among these five browsers.",
-    "P95 用来观察较慢的任务。Lightpanda 为 6.03 秒,Moli 为 6.55 秒,相差 0.53 秒;这一项 Lightpanda 更低。这里仅有 8 道题,按报告的计算方法,P95 就是各自最慢的一次,尚不足以判断长期运行中的尾部延迟。": "P95 describes slower tasks. Lightpanda records 6.03 seconds and Moli 6.55 seconds, a gap of 0.53 seconds; Lightpanda is lower on this measure. With only 8 tasks, the report’s calculation makes P95 each browser’s slowest result. This is not enough to estimate tail latency over long-term operation.",
-    "速度样本与计时口径": "Latency sample and timing definitions",
-    "内容与判分": "Content and scoring",
-    "一篇 PDF 怎样算读完,一张图片怎样算取到": "What counts as retrieving a PDF or an image",
-    "《Attention Is All You Need》这道题要求取得论文正文。只返回标题,或者只返回 PDF 地址,都不合格:输出需要包含指定文字,并覆盖论文各段的内容。换成图片任务,要求又可能是下载地址,也可能是图片文件本身。这些不同的交付要求,决定了每道题怎样判分。": "The Attention Is All You Need task requires the paper’s text. Returning only a title or PDF URL does not pass: the output must contain specified text and cover regions of the paper. An image task may instead require a download URL or the image file itself. These delivery requirements determine how each task is scored.",
-    "正文与文件参考来自独立获取的来源;商品列表检查经独立确认的稳定文字与结构,不把每天变化的商品名单固定成答案。正文覆盖阈值是本评测采用的工程验收下限,尚未通过阈值敏感性实验校准。当前成绩采用统一修订、离线重算后的规则。": "Text and file references come from independently retrieved sources. Product lists are checked against independently confirmed stable text and structure, without freezing a changing daily catalog. Body-coverage thresholds are engineering acceptance floors for this benchmark and have not been calibrated through threshold-sensitivity experiments. Current scores use consistently revised rules applied through offline rescoring.",
-    "选型时,任务构成会改变这张榜单的意义。文档与媒体占本轮题量的 20%,Moli 在这 30 道里比 Chromium 多通过 10 道;如果应用只读普通网页,对应成绩就是 97/100 与 99/100。需要云端部署的团队,还要结合并发和成本测试;本轮未测量这两项。": "The task mix matters when choosing a browser. Documents and media make up 20% of this dataset; Moli passes 10 more of those 30 tasks than Chromium. For an application reading only general web pages, the relevant scores are 97/100 and 99/100. Teams needing cloud deployment also need concurrency and cost tests, neither of which was measured here.",
-    "未通过的任务": "Failed tasks",
-    "长网页的验证页,动态页面的等待": "Verification pages on long documents, waiting for dynamic content",
-    "三份 W3C 标准,返回的都是 475 字符验证页": "Three W3C standards return the same 475-character verification page",
-    "98 道有完整可比参考的普通网页中,Moli 平均找回 95.3% 的参考正文词语,Chromium 为 99.8%。这个比例称为 Recall。另一个指标 Precision 检查输出词语中有多少与参考重合,F1 综合两者;它们衡量文字重合,不判断文字中的事实是否正确。": "Across 98 general pages with complete, comparable references, Moli recovers an average of 95.3% of reference words, versus Chromium’s 99.8%. This proportion is recall. Precision measures how much of the output overlaps the reference, and F1 combines the two. These metrics measure text overlap, not factual correctness.",
-    "后续评测": "Future evaluations",
-    "从 150 道读取任务,走向连续网页操作": "From 150 retrieval tasks to sequences of web interactions",
-    "后续会扩大站点与文档样本。延迟列表、iframe,以及本轮未通过的 DOCX 和嵌入式文档查看器,需要在迭代后重新验证;热门站点需要重复运行,速度比较也需要更多共同成功样本。": "Future tests will include more sites and documents. Delayed lists, iframes, DOCX, and embedded document viewers that failed this run need retesting after updates. Popular sites need repeated runs, and latency comparisons need more shared successful tasks."
-})
-
-
-TEXT.update({
-    "这里比较的是供程序调用的浏览器及其内容提取配置。Moli 面向 AI Agent,Chromium 是通用浏览器,本轮以不显示窗口的无头模式运行。它们通常将网页转换成 Markdown,也就是带少量格式标记的文字,交给 AI 使用。": "We compare browsers called by software, together with their content-extraction configurations. Moli is built for AI agents. Chromium is a general-purpose browser, run here in headless mode without a visible window. These paths typically convert pages into Markdown—text with lightweight formatting—for AI to use.",
-    "测速从上述 150 道任务的普通网页中预选 20 道,单独安排同一时间窗口运行。完整报告包含三个 Moli 版本,共七个配置;其中大家都成功读取的网页有 8 道。本文沿用这 8 道比较五款浏览器的速度:Moli 平均 3.41 秒,Chromium 5.28 秒,Lightpanda 3.60 秒。只比较相同的成功任务,可以排除快速返回空内容对成绩的影响。": "The latency test preselects 20 general pages from the 150 tasks and runs them separately within one time window. The full report includes three Moli versions, for seven configurations; all seven successfully retrieve 8 pages. This article uses those same 8 to compare the five browsers: mean latency is 3.41 seconds for Moli, 5.28 for Chromium, and 3.60 for Lightpanda. Comparing identical successful tasks prevents quick empty responses from improving the results.",
-    "Moli 的 P50 为 2.70 秒,Chromium 为 3.63 秒,减少 25.8%。均值与 P50 两项,Moli 都是这五款中最低。": "Moli’s P50 is 2.70 seconds versus Chromium’s 3.63 seconds, a 25.8% reduction. Moli has the lowest mean and P50 among these five browsers.",
-    "预选任务按参考正文长度分层,并覆盖不同域名。每个配置每题运行一次,计时覆盖从输入网址到返回内容的过程,包括内容提取与后处理;Kitesurf 通过远端服务调用,还包含网络往返。": "Preselected tasks are stratified by reference-text length and cover different domains. Each configuration runs each task once. Timing covers the input URL through returned content, including extraction and post-processing. Kitesurf is called through a remote service, so its time also includes network round trips.",
-    "20 道预选任务与 8 道共同成功任务的名单、单次耗时,均可在完整报告的测速页查看。": "The full report’s latency page lists all 20 preselected tasks, the 8 shared successes, and individual run times.",
-    "每款配置每题保留一次主结果。完整报告提供每道题的参考内容、实际输出和各项判定,便于逐条复查。": "Each configuration retains one primary result per task. The full report provides each task’s reference, actual output, and item-level verdicts for inspection.",
-    "PDF 和原文件交付解释了 Moli 在这轮混合任务中的增益。下一步需要验证:增加站点与文档类型后,这些成功能否重复,现有的读取缺口能否补齐。": "PDF and original-file delivery explain Moli’s gains in this mixed task set. The next step is to test whether those successes repeat across more sites and document types, and whether the current retrieval gaps can be closed.",
-    "延迟列表、iframe,以及本轮未通过的 DOCX 和嵌入式文档查看器,需要在迭代后重新验证;热门站点需要重复运行,速度比较也需要更多共同成功样本。": "Delayed lists, iframes, DOCX, and embedded document viewers that failed this run need retesting after updates. Popular sites need repeated runs, and latency comparisons need more shared successful tasks.",
-    "标题与注意力机制 · 原文行节选": "Title and attention mechanism · original line excerpts",
-    ";不得返回安全验证或拒绝访问页": "; must not return a security-verification or access-denied page",
-    "命中拦截页特征": "Block-page signature detected",
-    "未命中排除项或拦截页特征": "No excluded text or block-page signature detected",
-    "链接识别细节": "Link parsing details",
-    "两侧均为节选;评分使用完整正文。参考正文按词数从头到尾近似等分为五段,再检查每段连续六字符片段在输出中找回的比例。Moli 取得 PDF 后,由本地 pypdf 在同一预算内提取文字。": "Both sides show excerpts; scoring uses the complete text. The reference is divided from start to finish into five approximately equal regions by word count. Each region is checked for the proportion of contiguous six-character fragments recovered in the output. After Moli retrieves the PDF, local pypdf extracts the text within the same budget."
-})
-
-
-
-
-TEXT.update({
-    "Moli 取得 PDF": "Moli retrieves the PDF in",
-    "→ pypdf 提取正文": "→ pypdf extracts the text in",
-    "从输入到交付共 2.44 秒,包含流程开销。": "Total input-to-output time: 2.44 seconds, including processing overhead.",
-    "商品名称、价格和排序不作为固定答案;本题不核验名称的事实正确性或信息时效。右侧节选不是完整列表。后续三次固定诊断调用中,Moli 一次取得 72 条记录,两次返回空内容;单次通过尚不能说明长期稳定性。": "Product names, prices, and ordering are not fixed answers. This task does not verify the factual accuracy of names or information freshness. The excerpt on the right is not the complete list. In three subsequent fixed diagnostic calls, Moli retrieved 72 records once and returned empty content twice. A single pass does not establish long-term reliability.",
-    "文件长度的通用门槛是 32 字节。SHA-256 是按文件字节计算的指纹,用来核对是否同一份文件;不评价图片识别或媒体语义。同组另外两道原文件题也通过:WebM 330,618 字节,用时 1.83 秒;MP4 1,128,375 字节,用时 1.55 秒,均与独立目标文件哈希一致。": "The general file-length threshold is 32 bytes. SHA-256 is a fingerprint calculated from the file’s bytes to check file identity; it does not assess image recognition or media semantics. Two other original-file tasks in this group also pass: a 330,618-byte WebM in 1.83 seconds and a 1,128,375-byte MP4 in 1.55 seconds. Both hashes match the independently obtained target files."
-})
-
-
-TEXT.update({
-    "本题预先声明为 PDF 文档,未测试未知链接的自动类型识别。": "This task is declared as a PDF document in advance; automatic type detection for an unknown URL was not tested.",
-    "这三次用于观察波动,不替换主榜的那次结果。": "These three calls observe variability and do not replace the result used in the main ranking.",
-    "两侧均为节选;评分使用完整正文。参考正文按词数从头到尾近似等分为五段,再检查每段连续六字符片段在输出中找回的比例。Moli 取得 PDF 后,由本地 pypdf 在同一预算内提取文字。本题预先声明为 PDF 文档,未测试未知链接的自动类型识别。": "Both sides show excerpts; scoring uses the complete text. The reference is divided from start to finish into five approximately equal regions by word count. Each region is checked for the proportion of contiguous six-character fragments recovered in the output. After Moli retrieves the PDF, local pypdf extracts the text within the same budget. This task is declared as a PDF document in advance; automatic type detection for an unknown URL was not tested.",
-    "商品名称、价格和排序不作为固定答案;本题不核验名称的事实正确性或信息时效。右侧节选不是完整列表。后续三次固定诊断调用中,Moli 一次取得 72 条记录,两次返回空内容;单次通过尚不能说明长期稳定性。这三次用于观察波动,不替换主榜的那次结果。": "Product names, prices, and ordering are not fixed answers. This task does not verify the factual accuracy of names or information freshness. The excerpt on the right is not the complete list. In three subsequent fixed diagnostic calls, Moli retrieved 72 records once and returned empty content twice. A single pass does not establish long-term reliability. These three calls observe variability and do not replace the result used in the main ranking."
-})
-
-
-TEXT.update({
-    "98 道共同成功任务 · 主评测均值": "98 shared successes · main evaluation mean",
-    "Chromium:5.27 秒": "Chromium: 5.27 seconds",
-    "历史主结果,调用跨批次;详见测速分析": "Recorded primary results span batches; see the latency analysis",
-    "9 道共同成功网页 · 同窗测速 P50": "9 shared web successes · same-window P50",
-    "Chromium:3.87 秒,Moli 减少 27.2%": "Chromium: 3.87 seconds; Moli is 27.2% lower",
-    "六组真实案例": "Examples from six groups",
-    "98 道共同成功任务,Moli 的平均耗时为 3.01 秒": "98 shared successful tasks: Moli averages 3.01 seconds",
-    "主评测:全部 98 道共同成功任务 · 秒": "Main evaluation: all 98 shared successes · seconds",
-    "这 98 道中,Moli 的均值为 3.01 秒、P50 为 2.44 秒、P95 为 6.03 秒,三项都是五款中最低。但网络和运行时间不同,不能仅凭这组记录确定差距有多少来自浏览器实现。": "Across these 98 tasks, Moli records a mean of 3.01 seconds, P50 of 2.44 seconds, and P95 of 6.03 seconds, the lowest of the five on all three measures. Network conditions and execution times differ, so these records alone cannot establish how much of the gap comes from browser implementation.",
-    "9 道同窗测速:Moli 的 P50 为 2.82 秒": "9 same-window tasks: Moli records a P50 of 2.82 seconds",
-    "为进一步观察速度,我们从主评测的普通网页中预选 20 道,另行安排在同一个时间窗口执行。其中五款都成功读取的有 9 道,下面比较这 9 个相同网页。": "To examine speed further, we preselected 20 general pages from the main evaluation and ran them separately within the same time window. All five browsers successfully retrieved 9 pages. The comparison below uses those same 9 pages.",
-    "同窗测速:9 道共同成功网页 · 秒": "Same-window latency: 9 shared web successes · seconds",
-    "绿色标记该项最低值。计时包含内容提取与后处理;远端 Kitesurf 还包含网络往返。P50 为中位数;本组仅 9 道,按报告算法,P95 等于各自最慢的一道。": "Green marks the lowest value in each measure. Timing includes content extraction and post-processing; remote Kitesurf calls also include network round trips. P50 is the median. With only 9 tasks, the report’s P95 calculation gives each browser’s slowest task.",
-    "差异集中在一张 Sponsors 页面:Moli 成功取得内容,但花了 12.84 秒,Lightpanda 用了 5.59 秒。这次慢调用把 Moli 的均值和 P95 拉高,却没有改变 P50 最低的结果。对通常要等多久、偶尔会等多长,两类指标给出了不同的信息。": "The difference centers on a Sponsors page. Moli successfully retrieved its content in 12.84 seconds; Lightpanda took 5.59 seconds. This slow call raised Moli’s mean and P95 while leaving its P50 the lowest. Typical waits and occasional slow calls tell different parts of the story.",
-    "测速样本与计算方法": "Latency samples and calculations",
-    "两组统计都取本文五款浏览器共同通过的全部任务,不混用各自成功的不同网页。主评测为 98 道,同窗测速为预选 20 道中的 9 道。完整报告另保留其他 Moli 版本,可按受测配置查看其对应样本。": "Both sets use every task passed by all five browsers in this article, without mixing different successful pages. The main evaluation has 98 shared successes; the same-window test has 9 out of 20 preselected tasks. The full report also retains other Moli versions, with samples available by tested configuration.",
-    "均值是所有耗时的算术平均;P50 是分布中点;P95 观察较慢一端。显示值保留两位小数,差值与百分比使用未舍入值计算。主评测的跨批次采集与同窗测速的小样本,都限制了对长期运行速度的推断。": "The mean is the arithmetic average of all latencies; P50 is the midpoint of the distribution; P95 describes its slower end. Displayed values use two decimal places; differences and percentages use unrounded values. Collection across batches in the main evaluation and the small same-window sample both limit conclusions about long-term speed.",
-    "网页、列表和文件,怎样交给 AI": "Delivering pages, lists, and files to AI",
-    "Web Fetch 的输入都是地址,交付物却不一样。读论文要正文,整理商品要记录,下载图片可能要地址,也可能要文件本身。五种常见任务分别经过下面的处理步骤;浏览器可以采用不同实现,最后按同样的交付要求验收。": "Web Fetch always starts with an address, but the required output varies. Reading a paper needs text; organizing products needs records; an image task may need a URL or the file itself. The five common task types below follow different processing steps. Browsers may implement them differently, but their outputs face the same acceptance requirements.",
-    "取页、等待内容": "Retrieve page and wait for content",
-    "提取正文": "Extract body text",
-    "例如 Example Domain:标题、用途说明和更多信息入口都要保留。": "Example Domain: retain the title, purpose statement, and further-information link.",
-    "取得文件": "Retrieve file",
-    "例如论文 PDF:正文要覆盖指定区域,只有文件地址不够。": "Paper PDF: text must cover specified regions; a file URL alone is insufficient.",
-    "列表页": "List page",
-    "提取名称与字段": "Extract names and fields",
-    "检查完整、去重": "Check completeness and deduplicate",
-    "例如商品页:每条需要名称和详情链接,重复条目不增加数量。": "Product page: each record needs a name and detail link; duplicates do not add records.",
-    "媒体页面": "Media page",
-    "返回可用 URL": "Return a usable URL",
-    "例如视频演示页:找到指定视频地址,不能用导航链接代替。": "Video demo page: retrieve the specified video URL; a navigation link is insufficient.",
-    "文件直链": "Direct file URL",
-    "校验并交付": "Validate and deliver",
-    "例如 PNG:取得图片文件,后续工具才能识别或保存;图片说明不能代替文件。": "PNG: retrieve the image file so downstream tools can inspect or save it; an image description cannot replace the file.",
-    "六个评测集,各看一道真实任务": "One recorded task from each of six dataset groups",
-    "评测集按内容来源分成六组,和上面的交付类型相互交叉。例如动态网页可能要求列表,视频页面可能要求资源地址。每组取一道实测任务,核对独立参考、浏览器输出和具体判定;动态网页保留了失败例子。": "The dataset has six source-based groups, which overlap with the output types above. A dynamic page may require a list; a video page may require a resource URL. Each group has one recorded example with an independent reference, browser output, and item-level verdicts. The dynamic-page example is a failure.",
-    "失败与超时计入总题数": "Failures and timeouts remain in the total",
-    "五款浏览器的实现与实测差异": "Implementations and measured differences across five browsers",
-    "Moli 是面向 AI Agent 的 Rust 浏览器,提供网页获取与浏览器自动化接口。本轮网页输出原生 Markdown;直接文档与媒体可交付原始响应,PDF 再由本地 pypdf 提取文字。": "Moli is a Rust browser for AI agents, with interfaces for page retrieval and browser automation. In this run, it returns native Markdown for pages and raw responses for direct document and media URLs. Local pypdf then extracts PDF text.",
-    "文档 7/10、图片 10/10、视频 7/10,说明这条配置能处理网页之外的输入。普通网页为 97/100;三个 W3C 标准停在安全验证页,DOCX 和嵌入式文档查看器也有未完成任务。": "Scores of 7/10 for documents, 10/10 for images, and 7/10 for videos show that this configuration handles inputs beyond web pages. General pages score 97/100. Three W3C standards stop at security-verification pages, with additional failures involving DOCX and embedded document viewers.",
-    "Chromium 是通用开源浏览器,也是许多浏览器产品的基础。本轮以无头模式运行,读取 DOM(浏览器维护的页面结构),再用 node-html-markdown 2.0.0 转成 Markdown。": "Chromium is a general-purpose open-source browser and the basis for many browser products. Here it runs headlessly, reads the DOM—the page structure maintained by the browser—and converts it to Markdown using node-html-markdown 2.0.0.",
-    "普通网页通过 99/100,参考正文词语找回比例为 99.8%。文档为 1/10,这对应当前页面转文本路线;若增加文件下载与 PDF 解析,需要重新评测那条组合配置。": "It passes 99/100 general pages and recovers 99.8% of reference body words. Its document score is 1/10 under the current page-to-text path. Adding file downloading and PDF parsing would require evaluating that combined configuration again.",
-    "Lightpanda 是用 Zig 开发、面向 AI 与自动化的无头浏览器,并非 Chromium 分支。本轮在本地调用,直接取得原生 Markdown。": "Lightpanda is a headless browser written in Zig for AI and automation, rather than a Chromium fork. This run calls it locally and retrieves native Markdown.",
-    "普通网页通过 92/100,三篇 Moli 未完成的 W3C 标准都取得了正文,IMDb 题只有它通过。9 道同窗测速的均值为 3.82 秒、P95 为 6.03 秒,均为五款最低;文档则通过 1/10。": "It passes 92/100 general pages, retrieves all three W3C standards missed by Moli, and is the only browser to pass IMDb. Across 9 same-window latency tasks, its mean is 3.82 seconds and P95 is 6.03 seconds, both the lowest of the five. It passes 1/10 documents.",
-    "Obscura 是采用 V8 执行 JavaScript 的 Rust 无头浏览器。本轮使用本地标准模式的原生 Markdown,没有启用 stealth 模式或备用引擎。": "Obscura is a Rust headless browser that uses V8 to execute JavaScript. This run uses native Markdown in local standard mode, without stealth mode or an alternative engine.",
-    "普通网页通过 90/100,热门站点通过 5/10,也取得了 Moli 未交回的 Blender 下载资源入口。9 道同窗测速 P50 为 13.72 秒;这一等待时间只对应受测标准模式。": "It passes 90/100 general pages and 5/10 popular-site tasks, and retrieves the Blender download link missed by Moli. Its P50 across 9 same-window latency tasks is 13.72 seconds; this latency applies to the tested standard mode.",
-    "Kitesurf 是 Cloudflare 面向 Agent 的浏览器方案。本轮通过远端 Browser Run 接口提交 URL、返回 Markdown,浏览器在云端运行。": "Kitesurf is Cloudflare’s browser offering for agents. This run submits URLs through the remote Browser Run interface and receives Markdown, with the browser running in the cloud.",
-    "普通网页通过 88/100,动态网页通过 8/10,YouTube 题只有它通过。9 道同窗测速 P50 为 6.21 秒,包含网络往返与远端处理;本轮未测试托管成本、扩容和生产并发。": "It passes 88/100 general pages and 8/10 dynamic pages, and is the only browser to pass YouTube. Its P50 across 9 same-window tasks is 6.21 seconds, including network round trips and remote processing. Hosting cost, scaling, and production concurrency were not tested.",
-    "任务构成会改变这张榜单的意义。文档与媒体占本轮题量的 20%,Moli 在这 30 道里比 Chromium 多通过 10 道;如果应用只读普通网页,对应成绩就是 97/100 与 99/100。选择时应把自己的输入类型,与对应分组和失败案例一起看。": "The task mix changes how this ranking should be read. Documents and media make up 20% of the dataset; Moli passes 10 more of those 30 tasks than Chromium. For applications reading only general pages, the relevant scores are 97/100 and 99/100. Match your input types to the corresponding groups and failure cases.",
-    "Moli 也在继续迭代:1.1.2、1.1.3、1.1.4 分别于 9 月 4 日、5 日、8 日发布。1.1.4 的抓取修复包括流式解析暂停时保留重定向处理。版本变化说明开发仍在推进,但是否解决这里的延迟列表、iframe 或文档问题,要用同题复测确认。": "Moli continues to change: versions 1.1.2, 1.1.3, and 1.1.4 were released on 9/4, 5, and 8, respectively. Retrieval fixes in 1.1.4 include preserving redirect handling when streaming parsing pauses. These releases show ongoing development, but resolving the delayed-list, iframe, or document failures requires retesting the same tasks.",
-    "本组仅 9 题,按报告算法等于最慢一题": "With only 9 tasks, the report’s calculation gives the slowest task",
-    "95% 的任务耗时不超过这个值": "95% of task latencies do not exceed this value",
-    "从输入到交付共 2.44 秒,包含流程开销。本题预先声明为 PDF 文档,未测试未知链接的自动类型识别。": "Total input-to-output time: 2.44 seconds, including processing overhead. This task is declared as a PDF document in advance; automatic type detection for an unknown URL was not tested."
-})
-
-
-TEXT.update({
-    "Quotes 延迟加载的名言": "Quotes: quotations loaded after a delay",
-    "独立渲染后可见的第一条完整记录": "First complete record visible after independent rendering",
-    "查看全部 10 条独立参考记录": "View all 10 independent reference records",
-    "页面自动加载完成后应有 10 条名言,每条保留原句、作者与标签;无需滚动或点击。": "After automatic loading completes, the page should contain 10 quotations, each with its text, author, and tags. No scrolling or clicking is required.",
-    "返回 170 字符,用时 5.75 秒;输出里有标题、登录和翻页入口,没有名言记录。": "Returned 170 characters in 5.75 seconds. The output contains a title, login link, and pagination links, but no quotation records.",
-    "完整名言记录 0 条;固定名言命中 0/10 组。": "Complete quotation records: 0; fixed quotation groups matched: 0/10.",
-    "第一条原句": "First quotation’s text",
-    "第一条作者": "First quotation’s author",
-    "第一条标签": "First quotation’s tags",
-    "完整且不同的记录": "Complete, distinct records",
-    "至少 10 条,每条包含原句、作者和标签": "At least 10 records, each with quotation text, author, and tags",
-    "完整 0 条;去重后 0 条": "Complete: 0 records; after deduplication: 0 records",
-    "全部固定名言": "All fixed quotations",
-    "10 组原句、作者与标签均须匹配独立参考": "All 10 groups of quotation text, author, and tags must match the independent reference",
-    "排除:Access denied / CAPTCHA;不得返回安全验证或拒绝访问页": "Exclude: Access denied / CAPTCHA; must not return a security-verification or access-denied page",
-    "这个练习页将名言延迟呈现。调用正常返回,不等于目标内容已经到齐;Moli 与 Chromium 本轮都只取得了页面框架。等待与内容完成判断,是这类任务需要改进的环节。": "This practice page displays quotations after a delay. A completed call does not mean the requested content has arrived. Moli and Chromium both retrieve only the page shell in this run. Waiting and detecting content completion are areas to improve for this type of task.",
-    "MDN 视频播放文件": "MDN video playback file",
-    "独立源文档确认的视频文件地址": "Video file URL confirmed in an independent source document",
-    "应交付视频文件或有效播放器入口;封面图片与普通首页链接不算。": "Return the video file or a valid player entry point; a cover image or ordinary homepage link does not qualify.",
-    "以输入页面地址补全相对路径后": "After resolving relative paths against the input page URL",
-    "视频资源地址": "Video resource URL",
-    "输出虽含“不支持 HTML5 视频”的后备提示,仍提供了正确的播放文件链接。资源题检查目标地址,而不是把这句话当作抓取失败。本题 Moli 与 Chromium 均通过,不涉及视频听写或内容理解。": "The output includes fallback text saying HTML5 video is unsupported, but still provides the correct playback file link. This resource task checks the target URL; the fallback message alone does not mean retrieval failed. Moli and Chromium both pass this task, which does not test video transcription or comprehension."
-})
-
-
-TEXT.update({
-    "全部方法共同成功任务的耗时": "Latency on tasks passed by every method",
-    "全部 7 种受测配置共同通过": "All 7 tested configurations pass",
-    "个任务。下表每行使用这同一组任务;均值、P50、P95 的最低值加粗。": "tasks in common. Every row below uses this same task set; the lowest mean, P50, and P95 are shown in bold.",
-    "范围包含报告登记的全部方法与版本。统计沿用主评测的历史调用及已披露时延修正,调用跨日期、跨批次;这些数值不代表同一时间窗口内的受控速度比较,也不包含失败任务的耗时。": "This includes all methods and versions registered in the report. Statistics use recorded primary calls and disclosed latency corrections across dates and batches. These values are not a controlled speed comparison within one time window and exclude failed-task latency.",
-    "共同任务与调用日期": "Shared tasks and call dates",
-    "同题均值": "Mean on shared tasks",
-    "同题 P50": "P50 on shared tasks",
-    "同题 P95": "P95 on shared tasks",
-    "调用矩阵尚不完整,暂不汇总全部方法共同成功任务的耗时。": "The call matrix is incomplete, so latency on tasks passed by every method is not yet summarized.",
-    "全部方法尚无共同成功任务,不计算同题耗时。": "No task is yet passed by every method, so shared-task latency is not calculated."
-})
-
-
-TEXT.update({
-    "绿色标记该项最低值。计时包含内容提取与后处理;远端 Kitesurf 还包含网络往返。本组仅 9 道,按报告算法,P95 等于各自最慢的一道。": "Green marks the lowest value in each measure. Timing includes content extraction and post-processing; remote Kitesurf calls also include network round trips. With only 9 tasks, the report’s P95 calculation gives each browser’s slowest task.",
-    "显示值保留两位小数,差值与百分比使用未舍入值计算。主评测的跨批次采集与同窗测速的小样本,都限制了对长期运行速度的推断。": "Displayed values use two decimal places; differences and percentages use unrounded values. Collection across batches in the main evaluation and the small same-window sample both limit conclusions about long-term speed.",
-    "Moli 也在继续迭代:1.1.2、1.1.3、1.1.4 分别于 9 月 4 日、5 日、8 日发布。1.1.4 包含页面重定向处理修复。是否改善这里的延迟列表、iframe 或文档问题,仍需同题复测。": "Moli continues to change: versions 1.1.2, 1.1.3, and 1.1.4 were released on 9/4, 5, and 8, respectively. Version 1.1.4 includes fixes to page-redirect handling. Whether it improves the delayed-list, iframe, or document issues here requires retesting the same tasks.",
-    "应交付目标视频文件的地址或有效播放器入口;封面图片与普通首页链接不算。": "Return the target video file URL or a valid player entry point; a cover image or ordinary homepage link does not qualify.",
-})
-
-
-TEXT.update({
-    "Firecrawl · Scrape API 与 video 格式 ↗": "Firecrawl · Scrape API and video format ↗",
-    "Cloudflare · Scrape 端点 ↗": "Cloudflare · Scrape endpoint ↗",
-    "Cloudflare · 文档转 Markdown ↗": "Cloudflare · Document-to-Markdown conversion ↗",
-    "Cloudflare · 支持的文档格式 ↗": "Cloudflare · Supported document formats ↗",
-    "Cloudflare Browser Run /markdown、/scrape 与 Workers AI /ai/tomarkdown": "Cloudflare Browser Run /markdown and /scrape with Workers AI /ai/tomarkdown",
-    "Browser Run 可用 Workers Free 额度。Browser Run 需要 Browser Rendering Edit;文件转换 API 接受 Workers AI Read 或 Edit。两类产品的配额和计费分开。": "Browser Run has a Workers Free allowance. It requires Browser Rendering Edit; file conversion accepts Workers AI Read or Edit. The products have separate quotas and billing.",
-    "注册 Cloudflare,复制 Account ID;创建覆盖 Account → Browser Rendering → Edit 与 Account → Workers AI → Read 的自定义 token。官方 Workers AI REST 入门建议 Read 和 Edit 都授予;运行前用 GET /client/v4/user/tokens/verify 验证。": "Register with Cloudflare and copy the Account ID. Create a custom token covering Account → Browser Rendering → Edit and Account → Workers AI → Read. The Workers AI REST getting-started guide recommends both Read and Edit; verify the token at GET /client/v4/user/tokens/verify before running.",
-})
-
-
-PHRASES.update({
-    "原有资料核对日期:": "Existing sources checked: ",
-    ";本版新增 Cloudflare 与 Firecrawl 接口核对日期:": "; newly used Cloudflare and Firecrawl routes checked: ",
-    "。托管服务按官方文档;内置工具按固定源码。此处说明可选能力,": ". Managed services follow official documentation, while built-in tools follow pinned source. These are available capabilities, ",
-})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 
 def release_translations(history: dict) -> dict[str, str]:
     """Localize only the structured release records included in this report."""
@@ -2779,86 +306,12 @@ def add(source, target):
 ))
 
 
-TEXT.update({
-    "98 道共同成功任务 · 平均耗时": "98 shared successful tasks · mean latency",
-    "同组 98 道任务 · P50": "The same 98 tasks · P50",
-    "Chromium:4.58 秒": "Chromium: 4.58 seconds",
-    "同组 98 道任务 · P95": "The same 98 tasks · P95",
-    "Chromium:10.83 秒": "Chromium: 10.83 seconds",
-    "耗时来自跨批次采集的主评测记录,包含内容提取和后处理;网络与运行时间不同,不能当作受控条件下的内核速度差异。": "Latencies come from primary evaluation records collected across batches and include content extraction and post-processing. Network conditions and execution times differ, so these are not browser-engine speed differences measured under controlled conditions.",
-    "01 浏览器与 AI": "01 Browsers and AI",
-    "02 结果与速度": "02 Results and latency",
-    "03 任务与验收": "03 Tasks and acceptance",
-    "04 五款浏览器": "04 Five browsers",
-    "05 未完成的任务": "05 Incomplete tasks",
-    "06 总结与后续评测": "06 Findings and future evaluations",
-    "01 浏览器怎样把网页交给 AI": "01 How browsers deliver pages to AI",
-    "02 多通过的 10 道任务,来自文档与媒体": "02 Documents and media account for 10 additional passes",
-    "耗时样本与计算方法": "Latency sample and calculations",
-    "03 从任务要求到逐项验收": "03 From task requirements to item-level acceptance",
-    "04 五款浏览器的实现与实测差异": "04 Implementations and measured differences across five browsers",
-    "浏览器及定位": "Browser and purpose",
-    "实测长处": "Measured strengths",
-    "未完成或边界": "Failures and scope limits",
-    "面向 AI Agent 的 Rust 浏览器,提供获取与自动化接口": "Rust browser for AI agents, with retrieval and automation interfaces",
-    "文档 7/10、图片 10/10、视频 7/10": "Documents 7/10; images 10/10; videos 7/10",
-    "通用开源浏览器,许多浏览器产品以它为基础": "General-purpose open-source browser underlying many browser products",
-    "普通网页 99/100;98 道可比正文的平均词语找回比例 99.8%": "General pages 99/100; average reference-word recovery of 99.8% across 98 comparable text tasks",
-    "本地;原生 Markdown": "Local; native Markdown",
-    "文档 1/10;动态网页仍有 2/10 未完成": "Documents 1/10; 2/10 dynamic pages remain incomplete",
-    "采用 V8 执行 JavaScript 的 Rust 无头浏览器": "Rust headless browser using V8 to execute JavaScript",
-    "本地标准模式;原生 Markdown": "Local standard mode; native Markdown",
-    "未启用 stealth 模式或备用引擎;结果不覆盖这些配置": "Stealth mode and alternative engines were not enabled; results do not cover those configurations",
-    "远端 Browser Run;提交 URL,返回 Markdown": "Remote Browser Run; submit a URL and receive Markdown",
-    "普通网页 88/100、动态网页 8/10;YouTube 题仅它通过": "General pages 88/100; dynamic pages 8/10; the only browser to pass YouTube",
-    "计时包含网络往返;未测试托管成本、扩容和生产并发": "Timing includes network round trips; hosting cost, scaling, and production concurrency were not tested",
-    "05 尚未完成的网页与文档": "05 Pages and documents still incomplete",
-    "文档与媒体通过数为 24/30 对 14/30,构成 Moli 多通过的 10 道;120 道网页任务中,两者均为 111/120。": "Document and media passes are 24/30 versus 14/30, accounting for Moli’s 10 additional passes. Across 120 web tasks, both score 111/120.",
-    "98 道共同成功任务中,Moli 的均值、P50、P95 为 3.01、2.44、6.03 秒,Chromium 为 5.27、4.58、10.83 秒。Moli 三项均较低,但这组跨批次记录尚不能隔离网络和运行条件的影响。": "Across 98 shared successful tasks, Moli’s mean, P50, and P95 are 3.01, 2.44, and 6.03 seconds, versus Chromium’s 5.27, 4.58, and 10.83 seconds. Moli is lower on all three, but these records collected across batches cannot isolate network and execution conditions.",
-    "待补的缺口也具体:三份 W3C 标准的验证页、延迟列表、iframe 子页面,以及 DOCX 和嵌入式文档查看器。普通网页的正文词语找回比例,Moli 为 95.3%,Chromium 为 99.8%,还不能称为完全对齐。": "The remaining gaps are specific: verification pages on three W3C standards, delayed lists, iframe content, DOCX, and embedded document viewers. Reference-word recovery on general pages is 95.3% for Moli and 99.8% for Chromium, so they are not yet fully matched.",
-    "接下来验证什么": "What to test next",
-    "增加站点、文档类型和重复次数;在相同运行条件下扩大速度样本,观察热门站点的成功率波动。": "Add sites, document types, and repeated runs; expand the latency sample under the same execution conditions and observe success-rate variability on popular sites.",
-})
-
-
-
-
-TEXT.update({
-    "综合通过率 90.0%,五款中最高。": "Overall pass rate: 90.0%, the highest of the five.",
-    "多通过的 10 道,来自文档与媒体。": "Documents and media account for 10 additional passes.",
-    "这 30 道中,Moli 通过 24 道,Chromium 通过 14 道;120 道网页任务中,两者均为 111/120。": "Of these 30 tasks, Moli passes 24 and Chromium passes 14. Across 120 web tasks, both score 111/120.",
-    "98 道共同成功任务,三项耗时均较低。": "Lower on all three latency measures across 98 shared successes.",
-    "Moli 的均值、P50、P95 为 3.01、2.44、6.03 秒,Chromium 为 5.27、4.58、10.83 秒。这组跨批次记录尚不能隔离网络和运行条件的影响。": "Moli’s mean, P50, and P95 are 3.01, 2.44, and 6.03 seconds, versus Chromium’s 5.27, 4.58, and 10.83 seconds. These records collected across batches cannot isolate network and execution conditions.",
-})
-
-
-TEXT.update({
-    "Moli Web Fetch 实测:": "Moli Web Fetch benchmark:",
-    "平均耗时减少 · 相对 Chromium": "Mean latency reduction · versus Chromium",
-    "Moli 3.01 秒 / Chromium 5.27 秒": "Moli 3.01 seconds / Chromium 5.27 seconds",
-    "P50 耗时减少 · 相对 Chromium": "P50 latency reduction · versus Chromium",
-    "Moli 2.44 秒 / Chromium 4.58 秒": "Moli 2.44 seconds / Chromium 4.58 seconds",
-    "P95 耗时减少 · 相对 Chromium": "P95 latency reduction · versus Chromium",
-    "Moli 6.03 秒 / Chromium 10.83 秒": "Moli 6.03 seconds / Chromium 10.83 seconds",
-    "01 这次测什么": "01 What was tested",
-    "02 通过率与耗时": "02 Pass rates and latency",
-    "03 测试方法与实例": "03 Methods and examples",
-    "04 浏览器与测试配置": "04 Browsers and test configurations",
-    "05 失败任务与正文缺失": "05 Failed tasks and missing text",
-    "06 结论与后续测试": "06 Findings and further tests",
-    "98 道共同成功任务的耗时": "Latency across 98 shared successful tasks",
-    "网页正文:保留标题、必要内容和指定链接。": "Page text: retain the title, required content, and specified links.",
-    "文档正文:从 PDF 等文件解析文字,覆盖要求的正文区域。": "Document text: extract text from files such as PDFs and cover the required body regions.",
-    "列表记录:名称与字段完整,重复条目只计一次。": "List records: include complete names and fields; count duplicates only once.",
-    "媒体地址:返回目标图片或视频的可用 URL。": "Media URLs: return a usable URL for the target image or video.",
-    "原始文件:交付实际字节,核对格式、大小和文件指纹。": "Original files: deliver actual bytes and verify format, size, and file fingerprint.",
-    "六个评测集的实际结果": "Recorded results from six dataset groups",
-    "样本与判定规则": "Samples and acceptance rules",
-    "W3C 标准的访问验证": "Access verification on W3C standards",
-    "延迟加载与 iframe": "Delayed loading and iframes",
-    "后续测试": "Further tests",
-    "MDN 视频链接": "MDN video link"
-})
+
+
+
+
+
+
 
 
 REGEX.insert(0, (
@@ -2867,103 +320,7 @@ def add(source, target):
 ))
 
 
-TEXT.update({
-    "五款浏览器的通过率、Moli 相对 Chromium 的耗时减少比例,以及文档与媒体的通过数。": "Pass rates for five browsers, Moli’s latency reductions relative to Chromium, and document and media pass counts.",
-    "本轮结果汇总。耗时减少比例以 Chromium 为基准,取 98 道共同成功任务。": "Results from this run. Latency reductions use Chromium as the baseline across 98 shared successful tasks.",
-    "上方读取论文内容,下方搜索商品、筛选并加入购物车,对比 Web Fetch 与 Browser Use。": "Web Fetch retrieves a paper above; Browser Use searches, filters, and adds a product to the cart below.",
-    "以读论文和购物为例,说明读取内容与连续操作的区别。图中页面为流程示意。": "Reading a paper and shopping illustrate content retrieval versus a sequence of actions. Pages in the image are workflow illustrations.",
-    "五行分别展示网页正文、文档文字、列表记录、媒体地址和原始文件的获取与交付。": "Five rows show retrieval and delivery of page text, document text, list records, media URLs, and original files.",
-    "按最终交付物区分任务;图中页面为流程示意,下面的案例展示实测输出。": "Tasks are distinguished by their final outputs. Pages in the image illustrate workflows; the cases below show recorded outputs."
-})
-
-TEXT.update({
-    "打开完整调用记录": "Open complete call record",
-    "调用记录": "Call record",
-    "关闭调用记录": "Close call record",
-    "关闭": "Close",
-    "日志可用性:": "Log availability: ",
-    "完整事件日志已保存并通过 SHA-256 校验。": "A complete event log was retained and verified by SHA-256.",
-    "事件日志文件可以读取,但本条记录未声明其 SHA-256;页面会展示内容,但不能证明它与运行记录绑定。": "The event-log file is readable, but this record declares no SHA-256 for it. The page shows the content but cannot establish that it is bound to the run record.",
-    "事件日志文件可以读取,但至少一条记录未声明其 SHA-256;页面会展示内容,但不能证明全部日志均与运行记录绑定。": "Event-log files are readable, but at least one record declares no SHA-256. The page shows their content but cannot establish that every log is bound to the run record.",
-    "记录声明了事件日志,但公开证据中没有可读取文件;下面仅展示可核验的结果字段。": "The record declares an event log, but no readable file is present in the public evidence; only verifiable result fields are shown below.",
-    "这次历史运行没有保存统一事件日志。下面由最终输出、结果字段和公开适配器元数据合成,不能还原未记录的逐行过程。": "This historical run did not retain a unified event log. The view below is reconstructed from the final output, result fields, and public adapter metadata; it cannot recover unrecorded step-by-step events.",
-    "最后输出": "Final output",
-    "最后报错": "Final error",
-    "最后报错:": "Final error: ",
-    "无报错;请求记录为成功。": "No error was recorded; the request is recorded as successful.",
-    "请求失败,但历史记录未保存错误文字。": "The request failed, but the historical record retained no error text.",
-    "请求路线": "Request route",
-    "请求状态": "Request status",
-    "总耗时": "Total elapsed time",
-    "开始时间": "Started at",
-    "完成时间": "Completed at",
-    "物理调用": "Physical calls",
-    "原生事件日志": "Native event log",
-    "最终物理调用": "Final physical call",
-    "重试与物理调用": "Retries and physical calls",
-    "本次没有登记重试;只保留一个物理调用。": "No retry was declared; one physical call was retained.",
-    "查看公开适配器字段": "View public adapter fields",
-    "接口返回的公开附加字段": "Public fields returned by the adapter",
-    "原始供应商响应信封仅在内部证据中保留,不进入公开弹窗或发布包。": "The original provider response envelope is retained only in internal evidence and is excluded from the public dialog and publication bundle.",
-    "没有登记仅限内部保存的原始响应信封。": "No internal-only raw response envelope was registered.",
-    "最终输出确实为空。上游请求有响应,但适配器没有得到可提取的正文。": "The final output is genuinely empty. The upstream request returned a response, but the adapter obtained no extractable body text.",
-    "最终输出确实为空;本次历史记录没有保存更具体的原因,不能据此推断是服务端、网络还是适配器问题。": "The final output is genuinely empty. This historical record retained no more specific reason, so it cannot establish whether the cause was the server, network, or adapter.",
-    "—(未记录)": "— (not recorded)",
-    "—(未记录错误类型)": "— (error type not recorded)",
-    "查看运行日志与证据": "View run logs and evidence",
-    "运行日志与证据": "Run logs and evidence",
-    "这次历史调用没有保存逐行 debug 日志;不会补造不存在的日志。": "This historical call did not retain a line-by-line debug log; no missing log is reconstructed.",
-    "运行记录声明了日志文件,但公开证据中没有可读取文件;日志证据不完整。": "The run record declares a log file, but no readable file is present in the public evidence; log evidence is incomplete.",
-    "运行日志": "Run logs",
-    "最终输出": "Final output",
-    "查看错误与关键日志": "View errors and key log lines",
-    "没有可展示的逐行日志。": "No line-by-line log is available to display.",
-    "查看并下载完整保存证据": "View and download complete retained evidence",
-    "下载完整实际输出": "Download complete actual output",
-    "下载完整调用日志 JSON": "Download complete call-log JSON",
-    "没有可下载的逐行日志文件。": "No line-by-line log file is available to download.",
-    "查看重试与物理调用": "View retries and physical calls",
-    "原始供应商响应信封仅在内部证据中保留,不进入公开报告或发布包。": "The original provider response envelope is retained only in internal evidence and is excluded from the public report and publication bundle.",
-    "参与本段计分的完整实际返回行": "Complete actual-output lines used to score this region",
-    "评分器没有向这一段分配可展示的实际返回文字。": "The scorer assigned no displayable actual-output text to this region.",
-    "查看各段完整标准正文和计分行": "View complete reference text and scoring lines for each region",
-    "正文判定:": "Body-text rule: ",
-    "调用状态": "Call status",
-    "耗时": "Elapsed time",
-    "退出码": "Exit code",
-    "Markdown 输出": "Markdown output",
-    "日志是否完整": "Complete log retained",
-    "运行 ID": "Run ID",
-    "Moli 版本": "Moli version",
-    "查看完整运行字段": "View complete run fields",
-    "结束时间": "Finished at",
-    "日志级别": "Log level",
-    "公开日志": "Public log",
-    "公开日志脱敏": "Public log redacted",
-    "Moli SHA-256": "Moli SHA-256",
-    "正式命令": "Official command",
-    "已保存 stderr(级别未记录)": "stderr retained (level not recorded)",
-    "结论说明:": "Result note: ",
-    "这次调用正常形成了可评分输出。": "This call produced output that could be scored.",
-    "这次调用没有形成可通过的评分输出。": "This call did not produce output that could pass scoring.",
-    "查看本次真实日志": "View this run's real log",
-    "下载已保存的公开日志": "Download the retained public log",
-    "下载调用日志 JSON": "Download call-log JSON",
-    "下载本次调用记录": "Download this call record",
-    "下载本次保存的原始输出": "Download the raw output retained for this call",
-    "原始运行证据": "Original run evidence",
-    "是": "Yes",
-    "否": "No",
-    "否(公开保存的日志已截断)": "No (the retained public log is truncated)",
-    "不适用(没有保存 debug 日志)": "Not applicable (no debug log retained)",
-    "否(已截断)": "No (truncated)",
-    "—(调用记录未保存可展示的完整命令)": "— (the call record did not retain a complete displayable command)",
-    "这次历史调用没有保存可展示的 stderr / debug 日志;不会补造不存在的日志。": "This historical call retained no displayable stderr/debug log; no missing log is reconstructed.",
-    "这份公开日志是现有调用记录中保存的前段内容,原始记录注明其已截断。": "This public log contains the leading portion retained in the call record; that record marks it as truncated.",
-    "以下内容逐行摘自本次正式调用已保存的完整 stderr / debug 日志。": "The lines below come directly from the complete stderr/debug log retained for this formal call.",
-    "行号对应下载文件;人工说明不写入日志框。": "Line numbers correspond to the downloadable file; editorial explanation is kept outside the log box.",
-    "运行记录保留请求状态、耗时和证据路径;原始输出保持调用后保存的字节。": "The call record retains request status, timing, and evidence paths; the raw output preserves the bytes saved after the call.",
-})
+
 
 REGEX.insert(0, (
     re.compile(r"^(\d+) 份真实日志文件已保存并通过 SHA-256 校验。$"),
@@ -3041,91 +398,6 @@ def add(source, target):
 ))
 
 
-TEXT.update({
-    "CPU 与内存": "CPU and memory",
-    "CPU、内存与网络": "CPU, memory, and network",
-    "隔离资源证据": "Isolated resource evidence",
-    "CPU、内存、网络汇总与共同成功样本": "CPU, memory, and network summary with shared-success cases",
-    "资源子集逐次记录": "Per-call resource-cohort records",
-    "容器环境与镜像身份": "Container environment and image identity",
-    "资源测量边界": "Resource measurement boundary",
-    "四种本地方法资源消耗复核": "Resource-use review for four local methods",
-    "远程 Kitesurf 无法观测服务端 CPU、内存和上游网络;把失败结果的低消耗或宿主机进程采样混入比较,也会产生误导。": "Remote Kitesurf does not expose server-side CPU, memory, or upstream network use. Mixing in the low cost of failed results or host-process samples would also be misleading.",
-    "每次调用使用独立 Docker 容器、cgroup v2 和专用网络命名空间;资源汇总只比较四种本地方法共同通过的 15 个页面。80/80 条结果在隐藏机器判定后逐条复核,分歧 0、未决 0。": "Every call uses a dedicated Docker container, cgroup v2, and a dedicated network namespace. Resource aggregates compare only the 15 pages passed by all four local methods. All 80/80 results were reviewed with machine verdicts hidden: 0 disagreements and 0 unresolved.",
-    "50 ms 采样可能遗漏更短的内存和瞬时速率峰值;结果表示本次 arm64 Linux 容器中的客户端成本,不代表远程服务端成本、生产吞吐或 SLA。": "Sampling every 50 ms may miss shorter memory or instantaneous-rate peaks. Results describe client cost in this arm64 Linux container run, not remote server cost, production throughput, or an SLA.",
-    "最终报告纳入 750 次调用。任务与参考冻结后执行;每个方法内部串行,不复用个人登录状态。": "The final report includes 750 calls. Tasks and references were frozen before execution; calls within each method ran serially without reusing personal login state.",
-    "CPU、内存和网络使用独立的 20 题同窗队列,不回填历史正确率调用。每次调用在受限的 Linux 容器中运行,使用 cgroup v2 累计 CPU、独立网络命名空间累计收发字节,并以 50 ms 采样内存与瞬时速率。容器固定为 2 个逻辑 CPU、2 GiB 内存和 512 个 PID;宿主控制器与容器启停不计入。": "CPU, memory, and network use a separate 20-task same-window cohort and are not backfilled into historical correctness calls. Each call runs in a limited Linux container, using cgroup v2 cumulative CPU counters, a dedicated network namespace for received and sent bytes, and 50 ms samples for memory and instantaneous rates. Containers are fixed at 2 logical CPUs, 2 GiB memory, and 512 PIDs; the host controller and container lifecycle are excluded.",
-    "资源汇总只纳入当前五种配置共同通过的任务,避免把快速失败当成节省资源。四种本地配置覆盖容器内 worker、浏览器与转换过程;Kitesurf 只覆盖本地请求客户端,其远端 CPU、内存和上游流量不可见。缺少任一资源字段、非 Linux/cgroup v2、镜像或二进制身份变化、并发不为 1,都会阻断报告构建。": "Resource aggregates include only tasks passed by all five current configurations, avoiding credit for fast failures. The four local configurations cover the worker, browser, and conversion inside the container; Kitesurf covers only its local request client, while remote CPU, memory, and upstream traffic remain invisible. A missing resource field, a non-Linux/cgroup-v2 backend, an image or binary identity change, or concurrency other than 1 blocks the report build.",
-    "资源汇总只纳入四种本地配置共同通过的任务,避免把快速失败当成节省资源,并覆盖容器内 worker、浏览器与转换过程。Kitesurf 的远端 CPU、内存和上游流量不可见,因此不进入资源表。缺少任一资源字段、非 Linux/cgroup v2、镜像或二进制身份变化、并发不为 1,都会阻断报告构建。": "Resource aggregates include only tasks passed by all four local configurations, avoiding credit for fast failures, and cover the worker, browser, and conversion inside the container. Kitesurf's remote CPU, memory, and upstream traffic are invisible, so it is excluded from the resource table. A missing resource field, a non-Linux/cgroup-v2 backend, an image or binary identity change, or concurrency other than 1 blocks the report build.",
-    "CPU 与网络总量来自累计计数;内存峰值和速率分位数依赖 50 ms 采样,可能遗漏更短的尖峰。报告分别呈现 CPU、内存、网络和任务通过率,不合成总分。": "CPU and network totals use cumulative counters. Memory peaks and rate percentiles depend on 50 ms sampling and may miss shorter spikes. The report presents CPU, memory, network, and task pass rate separately rather than combining them into a composite score.",
-    "02 通过率、耗时与资源": "02 Pass rates, latency, and resources",
-    "客户端 CPU 与内存": "Client-side CPU and memory",
-    "客户端 CPU、内存与网络": "Client-side CPU, memory, and network",
-    "资源指标怎样测": "How resource metrics are measured",
-    "当前证据没有独立的客户端资源测量;不从耗时或进程体积推测 CPU 与内存。": "The current evidence has no independent client-side resource measurement; CPU and memory are not inferred from latency or process size.",
-    "当前证据没有独立的客户端资源测量;本文不从耗时反推 CPU 或内存。": "The current evidence has no independent client-side resource measurement; this article does not infer CPU or memory from latency.",
-    "资源指标待独立同题测量后补充。": "Resource metrics will be added only after an independent matched-task measurement.",
-    "平均 CPU 时间": "Mean CPU time",
-    "平均 CPU 占单核": "Mean CPU as one-core utilization",
-    "平均峰值内存": "Mean peak memory",
-    "峰值内存 P95": "Peak-memory P95",
-    "平均接收": "Mean received",
-    "平均发送": "Mean sent",
-    "测量范围": "Measurement scope",
-    "本地进程树": "Local process tree",
-    "本地隔离容器": "Isolated local container",
-    "独立资源 cohort 在四种本地配置共同通过的": "The independent resource cohort compares the same tasks across",
-    "个普通网页上比较同一任务。四种本地配置中,平均 CPU 时间最低为": "general pages passed by all four local configurations. Among those configurations, the lowest mean CPU time is",
-    ",平均峰值内存最低为": ", and the lowest mean peak memory is",
-    "仅本机请求客户端": "Local request client only",
-    "可比边界:": "Comparison boundary:",
-    "这里统计一次性 Linux 客户端容器的 cgroup 与独立网络命名空间,宿主控制器和容器启停不计入。Kitesurf 的远端浏览器 CPU、内存和远端上游流量不可见,所以它只展示本机请求客户端开销,不能拿来证明远端引擎更省资源。": "This measures the cgroup and dedicated network namespace of a disposable Linux client container; the host controller and container lifecycle are excluded. Kitesurf's remote-browser CPU, memory, and upstream traffic are invisible, so its row shows only local request-client cost and cannot establish that the remote engine uses fewer resources.",
-    "这里统计四种本地方法的一次性 Linux 客户端容器 cgroup 与独立网络命名空间,宿主控制器和容器启停不计入。Kitesurf 的远端浏览器 CPU、内存和远端上游流量不可见,因此不进入资源表或本地引擎资源排名。": "This measures the disposable Linux client-container cgroup and dedicated network namespace for the four local methods; the host controller and container lifecycle are excluded. Kitesurf's remote-browser CPU, memory, and upstream traffic are invisible, so it is excluded from the resource table and local-engine resource ranking.",
-    "资源数据来自四种本地方法的单独同题 cohort,不回填到历史正确率调用。每次调用启动一个受限的 Linux 客户端容器及独立网络;宿主控制器和容器启停不计入。CPU 与网络总量读取累计计数,内存与速率分位数按 50 ms 采样。": "Resource data comes from a separate matched-task cohort for the four local methods and is not backfilled into historical correctness calls. Each call starts a limited Linux client container with a dedicated network; the host controller and container lifecycle are excluded. CPU and network totals use cumulative counters, while memory and rate percentiles are sampled every 50 ms.",
-    "资源汇总只使用四种本地配置共同通过的任务,避免把快速失败或空输出当成节省资源。短于采样间隔的内存或速率峰值可能遗漏;Kitesurf 等远端服务的服务端 CPU、内存与上游流量不可观测,因而不进入资源表。": "Resource aggregates use only tasks passed by all four local configurations, avoiding credit for fast failures or empty output. Memory or rate peaks shorter than the sample interval may be missed; server-side CPU, memory, and upstream traffic for remote services such as Kitesurf are not observable, so they are excluded from the resource table.",
-    "CPU 时间是所有已观测进程消耗之和;平均 CPU 占单核可超过 100%。内存为同一采样时刻的进程组 RSS 总和;网络是目标进程累计收发字节。50 ms 采样可能遗漏更短的内存/CPU 尖峰;失败任务不进入本表。": "CPU time sums all observed processes; mean one-core utilization may exceed 100%. Memory is the process group's summed RSS at each sample, while network is cumulative bytes received and sent by target processes. Sampling every 50 ms may miss shorter memory or CPU spikes, and failed tasks are excluded from this table.",
-    "CPU 时间和网络总量来自容器累计计数;平均 CPU 占单核可超过 100%。内存与速率分位数按 50 ms 采样。更短的内存或速率尖峰可能遗漏;失败任务不进入本表。": "CPU time and network totals come from cumulative container counters; mean one-core utilization may exceed 100%. Memory and rate percentiles are sampled every 50 ms. Shorter memory or rate spikes may be missed, and failed tasks are excluded from this table.",
-    "资源数据来自单独的同题 cohort,不回填到历史正确率调用。每次调用启动一个独立客户端进程组,以 50 ms 间隔观察该进程及所有子进程;CPU 时间累计所有已观测进程,RSS 内存按同一时刻求和,网络按目标进程累计收发字节。平均 CPU 占单核可以超过 100%,表示同时使用多个逻辑核。": "Resource data comes from a separate matched-task cohort and is not backfilled into historical correctness calls. Each call starts an isolated client process group sampled every 50 ms with all descendants; CPU time accumulates across observed processes, RSS is summed at each instant, and network uses cumulative bytes received and sent by target processes. Mean one-core CPU may exceed 100%, indicating concurrent use of multiple logical cores.",
-    "资源汇总只使用所有展示配置共同通过的任务,避免把快速失败或空输出当成节省资源。短于采样间隔的内存/CPU 峰值可能遗漏;远端服务的服务端 CPU、内存与上游流量不可观测。": "Resource aggregates use only tasks passed by every displayed configuration, avoiding credit for fast failures or empty output. Memory or CPU spikes shorter than the sample interval may be missed; server-side CPU, memory, and upstream traffic for remote services are not observable.",
-    "资源数据来自单独的同题 cohort,不回填到历史正确率调用。每次调用启动一个受限的 Linux 客户端容器及独立网络;宿主控制器和容器启停不计入。CPU 与网络总量读取累计计数,内存与速率分位数按 50 ms 采样。": "Resource data comes from a separate matched-task cohort and is not backfilled into historical correctness calls. Each call starts a limited Linux client container with a dedicated network; the host controller and container lifecycle are excluded. CPU and network totals use cumulative counters, while memory and rate percentiles are sampled every 50 ms.",
-    "资源汇总只使用所有展示配置共同通过的任务,避免把快速失败或空输出当成节省资源。短于采样间隔的内存或速率峰值可能遗漏;远端服务的服务端 CPU、内存与上游流量不可观测。": "Resource aggregates use only tasks passed by every displayed configuration, avoiding credit for fast failures or empty output. Memory or rate peaks shorter than the sample interval may be missed; server-side CPU, memory, and upstream traffic for remote services are not observable.",
-    "本轮资源效率领先": "Resource-efficiency leader in this run",
-    "本轮资源效率并列领先": "Resource-efficiency co-leaders in this run",
-    "个可按低消耗比较的指标最低。资源结果与任务通过率分别判断,不合成总分。": "lower-is-better resource metrics. Resource results and task pass rate are judged separately and are not combined into a composite score.",
-    "CPU 领先者": "CPU leader",
-    "内存领先者": "Memory leader",
-    "网络流量领先者": "Network-traffic leader",
-    "平均接收 / 平均发送": "Mean received / mean sent",
-    "接收": "Received",
-    "发送": "Sent",
-    "同题口径:所列配置共同通过": "Matched-task scope: general pages passed by every listed configuration:",
-    "所列配置共同通过": "Tasks passed by every listed configuration:",
-    ";这项交集只作附加诊断,不用于选择资源表的样本。各方法成功调用的条件统计另保留在资源证据中。": "; this intersection is diagnostic only and does not select the resource-table population. Conditional statistics for each method's successful calls are retained in the resource evidence.",
-    "资源主表纳入预先确定的全部任务,包括失败调用,统计容器内调用进程、浏览器与转换过程的消耗;同时列出各方法通过数。各方法成功调用另作条件统计,共同通过的任务仅作诊断。Kitesurf 的远端 CPU、内存和上游流量不可见,因此不进入资源表。缺少任一资源字段、非 Linux/cgroup v2、镜像或二进制身份变化、并发不为 1,都会阻断资源包构建。": "The primary resource table includes every predeclared task, including failed calls, and measures the caller process, browser, and conversion within the container. Each method's pass count is shown. Successful calls have separate conditional statistics; shared successes are diagnostic only. Kitesurf's remote CPU, memory, and upstream traffic are unobservable, so it is excluded. Missing resource fields, a non-Linux/cgroup-v2 backend, changed image or binary identity, or concurrency other than one block resource packaging.",
-    "个普通网页;失败任务不进入资源排名。": "pages; failed tasks do not enter the resource ranking.",
-    "CPU 与计算": "CPU and compute",
-    "内存": "Memory",
-    "网络": "Network",
-    "绿色标记表示该项消耗最低。": "Green marks the lowest resource use for that metric.",
-    "流量总量按低值标记;速率仅描述传输形态,不判赢家。": "Traffic totals mark lower values; rates only describe transfer behavior and do not determine a winner.",
-    "指标": "Metric",
-    "本项最低": "Lowest",
-    "CPU 时间 P50": "CPU time P50",
-    "CPU 时间 P95": "CPU time P95",
-    "任务内 CPU P95": "Within-task CPU P95",
-    "平均内存": "Mean memory",
-    "任务内存 P95": "Within-task memory P95",
-    "平均内存占用量": "Mean memory integral",
-    "接收 P95": "Received P95",
-    "发送 P95": "Sent P95",
-    "平均接收速率": "Mean receive rate",
-    "平均发送速率": "Mean send rate",
-    "CPU 与计算资源对比": "CPU and compute resource comparison",
-    "内存资源对比": "Memory resource comparison",
-    "网络资源对比": "Network resource comparison",
-    "每次调用使用一次性 Linux 客户端容器、cgroup v2 与独立网络命名空间;宿主控制器和容器启停不计入。Kitesurf 的远端浏览器 CPU、内存和远端上游流量不可见,因此不进入资源表或本地引擎资源排名。": "Each call uses a disposable Linux client container, cgroup v2, and a dedicated network namespace; the host controller and container lifecycle are excluded. Kitesurf's remote-browser CPU, memory, and upstream traffic are invisible, so it is excluded from the resource table and local-engine resource ranking.",
-    "左右滑动查看完整对比": "Scroll horizontally for the full comparison",
-})
 
 REGEX.insert(0, (
     re.compile(r"^资源表覆盖预先确定的全部 (\d+) 个普通网页,包含失败调用。这组任务的通过数:(.*)。各项平均值、P50、P95 使用同一完整范围。$"),
@@ -3200,11 +472,6 @@ def add(source, target):
     (re.compile(r"^(\d+) 个案例 · (\d+) 次调用,机器判分。本轮未重新核验来源。$"), r"\1 cases · \2 calls, machine-scored. Sources were not reverified for this run."),
     (re.compile(r"^每种方法均使用同一组 (\d+) 个任务,包含通过、未通过、错误和超时。耗时是单次调用从输入到返回的时间,不包含调用之间的统一冷却间隔。各方法串行执行,仍受本地与远程环境、网络和产品内部限制影响。$"), r"Each method uses the same \1 tasks, including passes, failures, errors, and timeouts. Latency runs from input to return for a single call, excluding shared cooldown intervals. Each method runs serially and remains subject to local and remote environments, networks, and product limits."),
 ]
-TEXT.update({
-    '普通正文使用 Markdown;动态完成条件与正文提取范围分离。直接文档使用 Moli 原生正文接口;文件使用原生 CDP 的原始响应;附件事件使用同一二进制的原始附件接口,计入同一时限。图片、视频资源只从已取得的 DOM 中读取,不另抓模块挖掘链接。文档原生文字读取计入同一截止预算;私网保护默认开启,只对冻结公开测试域名使用精确白名单;不在失败后切换格式或借用其他 provider。': 'Ordinary body text uses Markdown; readiness conditions are separate from extraction scope. Direct documents use Moli native body output. Files use native CDP responses; attachment events use the same binary’s raw attachment interface within the deadline. Image and video resources are read only from the retrieved DOM, without fetching modules for more links. Native document text retrieval shares the deadline. Private-network protection is enabled by default, with exact exceptions for frozen public test domains. Failures do not trigger format changes or another provider.',
-    '读取预声明范围;资源和嵌入文档使用各浏览器共用的脚本': 'Read the declared scope; resource and embedded-document tasks use the same scripts across browsers.',
-    '普通正文使用 Markdown;动态完成条件与正文提取范围分离。直接文档与文件使用 Moli 原生 CDP 的原始响应;附件事件使用同一二进制的原始附件接口,计入同一时限。图片、视频资源只从已取得的 DOM 中读取,不另抓模块挖掘链接。文档解析计入同一截止预算;私网保护默认开启,只对冻结公开测试域名使用精确白名单;不在失败后切换格式或借用其他 provider。': 'Ordinary pages use Markdown; readiness conditions are separate from extraction scope. Direct documents and files use Moli native CDP responses; attachment events use the same binary’s raw attachment interface within the same deadline. Image and video resources are read only from the retrieved DOM, without crawling modules for more links. Document parsing shares the deadline. Private-network protection is enabled by default, with exact exceptions for frozen public test domains. Failures do not trigger format changes or another provider.',
-})
 
 REGEX.extend([
     (re.compile(r"^核验于 (\d{4}-\d{2}-\d{2})。算力、网络、存储、代理和维护成本自担;没有测量这些成本,不把本地调用等同于零成本。官方托管产品或服务价格不属于本地执行模式。$"),
@@ -3476,298 +743,15 @@ def preserve(match):
     return _rewrite_links(localized, source, english)
 
 
-TEXT.update({
-    "安装与账号准备": "Installation and account setup",
-    "账号与安装 · Web Fetch": "Access & setup · Web Fetch",
-})
-
-TEXT.update({'内容选择': 'Content selection',
- '典型处理流程': 'Typical processing flow',
- '变更记录 · Web Fetch': 'Changelog · Web Fetch',
- '已知 URL → 渲染正文、结构化资源 URL 或原文件字节': 'Known URL → rendered body, structured resource URLs, or original file '
-                                   'bytes',
- '逐任务结果与证据': 'Per-task results and evidence',
- '运行参数与版本': 'Run parameters and versions',
- '本报告纳入 4,300 个逻辑结果。运行参数见下表;保留的物理调用与输出见各结果详情。': 'This report includes 4,300 logical results. Run parameters '
-                                                'are listed below; retained physical calls and outputs are '
-                                                'available in each result detail.',
- '固定选项': 'Fixed options',
- '页面完成条件:': 'Page completion condition:',
- '阻止访问私网:': 'Block private-network access:',
- '返回内容上限(字节):': 'Maximum returned bytes:',
- '调用间隔(秒):': 'Inter-call interval (seconds):',
- '日志级别:': 'Log level:',
- '完整保存诊断日志:': 'Retain full diagnostic logs:',
- '日志保存上限(字节):': 'Maximum retained log bytes:',
- '使用统一固定原生选项:': 'Use uniform fixed native options:',
- '域名解析方式:': 'DNS resolution mode:',
- '缓存最长年龄:': 'Maximum cache age:',
- '代理选择:': 'Proxy selection:',
- '请求输出格式:': 'Requested output formats:',
- '只取主要正文:': 'Main content only:',
- '提取深度:': 'Extraction depth:',
- '包含图片:': 'Include images:',
- '正文详细程度:': 'Text verbosity:',
- '缓存最长年龄(小时):': 'Maximum cache age (hours):',
- '包含资源链接:': 'Include resource links:',
- '具名链接数量上限:': 'Maximum named links:',
- '文档大小上限(字节):': 'Maximum document bytes:',
- '每次调用都保存了测试程序的调用记录:何时开始、传入什么参数、何时返回,以及是否发生错误。各案例详情中的“调用方事件”展示这些记录。': 'Every call retains harness events: '
-                                                                      'start time, input parameters, return '
-                                                                      'time, and errors. These appear under '
-                                                                      'Caller events in each case detail.',
- '“产品与依赖库日志”展示产品或底层库实际输出的运行信息。云端服务没有开放的内部日志,不会用测试程序的记录代替。下表说明各方法留下了哪些日志。': 'Product and dependency logs '
-                                                                           'contain runtime information '
-                                                                           'actually emitted by the product '
-                                                                           'or underlying libraries. Harness '
-                                                                           'events do not substitute for '
-                                                                           'unavailable cloud-internal logs. '
-                                                                           'The table lists what was '
-                                                                           'retained for each method.',
- '已启用产品 debug;日志文件另含调用方记录。': 'Product debug enabled; the log file also contains caller events.',
- '保留真实请求、返回错误及可用请求标识;未取得云服务内部 debug,后台能力尚未确认。': 'Actual requests, returned errors, and available request IDs '
-                                                'retained; cloud-internal debug was not obtained, and '
-                                                'backend capabilities remain unconfirmed.',
- '已启用 debug;实际原生日志可能很少,不能以日志文件存在推断内部过程完整。': 'Debug enabled; native logs may be sparse, and the presence of a '
-                                            'log file does not prove complete internal-process coverage.',
- '保留真实调用、返回和错误;此抓取路径未确认可开启内部 debug。': 'Actual calls, responses, and errors retained; internal debug '
-                                      'availability for this retrieval path is unconfirmed.',
- '保留底层 HTTP 库日志;不等同于完整产品内部执行日志。': 'Underlying HTTP-library logs retained; these are not complete '
-                                  'product-internal execution logs.',
- '启用原生 verbose 运行日志,并捕获进程输出。': 'Native verbose logging enabled, with process output captured.',
- '完整日志在各案例详情中查看。日志可以帮助定位问题,但没有记录的内部过程不能据此推断;日志文件存在,也不等于产品的全部内部过程都有记录。': 'Full retained logs are available in '
-                                                                        'each case detail. They help locate '
-                                                                        'problems, but unrecorded internal '
-                                                                        'processes cannot be inferred; a log '
-                                                                        'file does not mean every internal '
-                                                                        'process was recorded.',
- '以下命令从本轮保存的证据构建本地报告,不重新调用产品:': 'These commands build the local report from saved evidence without '
-                                'additional product calls:',
- '使用与本轮结果对应的证据目录。调用时的版本、配置和评分器哈希见': 'Use the evidence directory corresponding to these results. Call-time '
-                                    'versions, configuration, and scorer hashes are in',
- '调用前冻结协议': 'Pre-call frozen protocol',
- '。上面的构建命令读取保存的评分;修改评分规则后,应先按声明范围复评分,不能仅重建页面就称成绩已经更新。': '. The build commands read saved scores. After '
-                                                        'changing scoring rules, rescore the declared scope '
-                                                        'before rebuilding; rebuilding pages alone does not '
-                                                        'update scores.',
- '全部调用均值': 'All-call mean',
- '全部调用 P50': 'All-call P50',
- '全部调用 P95': 'All-call P95',
- '请求成功,并同时满足本例最低字符数、全部必需核对点、显著标记及结构要求。验证码或错误提示文字不单独决定任务是否通过。完整度、纯净度和 F1 另算,不作为通过门槛。 已登记正文覆盖规则的案例,还须在最多 5 个参考区域中逐区达到 80% 词语计数覆盖,不能只返回标题或开头。': 'The '
-                                                                                                                                             'request '
-                                                                                                                                             'must '
-                                                                                                                                             'succeed '
-                                                                                                                                             'and '
-                                                                                                                                             'satisfy '
-                                                                                                                                             'the '
-                                                                                                                                             'case '
-                                                                                                                                             'minimum '
-                                                                                                                                             'length, '
-                                                                                                                                             'every '
-                                                                                                                                             'required '
-                                                                                                                                             'checkpoint, '
-                                                                                                                                             'significant '
-                                                                                                                                             'marker, '
-                                                                                                                                             'and '
-                                                                                                                                             'structure '
-                                                                                                                                             'requirement. '
-                                                                                                                                             'Verification '
-                                                                                                                                             'or '
-                                                                                                                                             'error '
-                                                                                                                                             'text '
-                                                                                                                                             'does '
-                                                                                                                                             'not '
-                                                                                                                                             'independently '
-                                                                                                                                             'determine '
-                                                                                                                                             'acceptance. '
-                                                                                                                                             'Completeness, '
-                                                                                                                                             'purity, '
-                                                                                                                                             'and '
-                                                                                                                                             'F1 '
-                                                                                                                                             'are '
-                                                                                                                                             'separate '
-                                                                                                                                             'metrics, '
-                                                                                                                                             'not '
-                                                                                                                                             'pass '
-                                                                                                                                             'thresholds. '
-                                                                                                                                             'Cases '
-                                                                                                                                             'with '
-                                                                                                                                             'body-coverage '
-                                                                                                                                             'rules '
-                                                                                                                                             'must '
-                                                                                                                                             'reach '
-                                                                                                                                             '80% '
-                                                                                                                                             'token-count '
-                                                                                                                                             'coverage '
-                                                                                                                                             'in '
-                                                                                                                                             'each '
-                                                                                                                                             'of '
-                                                                                                                                             'up '
-                                                                                                                                             'to '
-                                                                                                                                             '5 '
-                                                                                                                                             'reference '
-                                                                                                                                             'regions; '
-                                                                                                                                             'a '
-                                                                                                                                             'title '
-                                                                                                                                             'or '
-                                                                                                                                             'opening '
-                                                                                                                                             'alone '
-                                                                                                                                             'is '
-                                                                                                                                             'insufficient.',
- '文档正文按请求状态、最低长度与全部内容核对点验收;只拿到下载链接不能通过。验证或错误提示只作诊断。F1另算,不作为通过门槛。 已登记正文覆盖规则的案例,还须在最多 5 个参考区域中逐区达到 80% 词语计数覆盖,不能只返回标题或开头。': 'Document '
-                                                                                                                          'text '
-                                                                                                                          'is '
-                                                                                                                          'accepted '
-                                                                                                                          'by '
-                                                                                                                          'request '
-                                                                                                                          'status, '
-                                                                                                                          'minimum '
-                                                                                                                          'length, '
-                                                                                                                          'and '
-                                                                                                                          'all '
-                                                                                                                          'content '
-                                                                                                                          'checkpoints; '
-                                                                                                                          'a '
-                                                                                                                          'download '
-                                                                                                                          'link '
-                                                                                                                          'alone '
-                                                                                                                          'is '
-                                                                                                                          'insufficient. '
-                                                                                                                          'Verification '
-                                                                                                                          'and '
-                                                                                                                          'error '
-                                                                                                                          'notices '
-                                                                                                                          'are '
-                                                                                                                          'diagnostic '
-                                                                                                                          'only. '
-                                                                                                                          'F1 '
-                                                                                                                          'is '
-                                                                                                                          'separate, '
-                                                                                                                          'not '
-                                                                                                                          'a '
-                                                                                                                          'pass '
-                                                                                                                          'threshold. '
-                                                                                                                          'Cases '
-                                                                                                                          'with '
-                                                                                                                          'body-coverage '
-                                                                                                                          'rules '
-                                                                                                                          'must '
-                                                                                                                          'reach '
-                                                                                                                          '80% '
-                                                                                                                          'token-count '
-                                                                                                                          'coverage '
-                                                                                                                          'in '
-                                                                                                                          'each '
-                                                                                                                          'of '
-                                                                                                                          'up '
-                                                                                                                          'to '
-                                                                                                                          '5 '
-                                                                                                                          'reference '
-                                                                                                                          'regions; '
-                                                                                                                          'a '
-                                                                                                                          'title '
-                                                                                                                          'or '
-                                                                                                                          'opening '
-                                                                                                                          'alone '
-                                                                                                                          'is '
-                                                                                                                          'insufficient.',
- '不支持输入类型': 'Unsupported input type',
- '任务规则未满足': 'Task requirements not met',
- '取页返回 404': 'Retrieval returned 404',
- '取页连接失败': 'Retrieval connection failed',
- '方法安全限制': 'Method safety restriction',
- '方法禁止跨站跳转': 'Method blocks cross-site redirects',
- '方法空页规则拦截': 'Method empty-page rule blocked retrieval',
- '正文提取为空': 'Body extraction empty',
- '源站要求验证码': 'Source site requires verification',
- '搜索': 'Search',
- '搜索案例、方法或原因': 'Search cases, methods, or reasons',
- '参考标注': 'Reference annotations',
- '任务验收': 'Task acceptance',
- '评分口径': 'Scoring scope',
- '核对请求是否成功,再逐项检查本例要求的稳定文字、正文覆盖与完整条目。验证或拦截提示只作诊断,出现提示不单独导致失败;只有页面外壳而没有合格目标内容仍不通过。': 'Check request success, '
-                                                                                   'then validate the '
-                                                                                   'required stable text, '
-                                                                                   'body coverage, and '
-                                                                                   'complete records. '
-                                                                                   'Verification or blocking '
-                                                                                   'notices are diagnostic '
-                                                                                   'only and do not '
-                                                                                   'independently cause '
-                                                                                   'failure; a page shell '
-                                                                                   'without qualifying '
-                                                                                   'target content still '
-                                                                                   'fails.',
- '接口返回 → 检查目标内容是否为空 → 核对题目要求的文字和字段 → 检查列表数量、类型与唯一性 → 汇总任务结果。只执行该例适用的检查;失败详情保留具体未满足项。图片与视频沿用请求和交付检查,资源地址或文件字节另按格式核验。': 'Response '
-                                                                                                                      '→ '
-                                                                                                                      'check '
-                                                                                                                      'for '
-                                                                                                                      'empty '
-                                                                                                                      'target '
-                                                                                                                      'content '
-                                                                                                                      '→ '
-                                                                                                                      'verify '
-                                                                                                                      'required '
-                                                                                                                      'text '
-                                                                                                                      'and '
-                                                                                                                      'fields '
-                                                                                                                      '→ '
-                                                                                                                      'check '
-                                                                                                                      'list '
-                                                                                                                      'count, '
-                                                                                                                      'types, '
-                                                                                                                      'and '
-                                                                                                                      'uniqueness '
-                                                                                                                      '→ '
-                                                                                                                      'aggregate '
-                                                                                                                      'the '
-                                                                                                                      'task '
-                                                                                                                      'verdict. '
-                                                                                                                      'Only '
-                                                                                                                      'checks '
-                                                                                                                      'applicable '
-                                                                                                                      'to '
-                                                                                                                      'that '
-                                                                                                                      'case '
-                                                                                                                      'run; '
-                                                                                                                      'failure '
-                                                                                                                      'details '
-                                                                                                                      'retain '
-                                                                                                                      'unmet '
-                                                                                                                      'requirements. '
-                                                                                                                      'Images '
-                                                                                                                      'and '
-                                                                                                                      'videos '
-                                                                                                                      'use '
-                                                                                                                      'request '
-                                                                                                                      'and '
-                                                                                                                      'delivery '
-                                                                                                                      'checks, '
-                                                                                                                      'with '
-                                                                                                                      'resource '
-                                                                                                                      'URLs '
-                                                                                                                      'or '
-                                                                                                                      'file '
-                                                                                                                      'bytes '
-                                                                                                                      'validated '
-                                                                                                                      'by '
-                                                                                                                      'format.'})
+
 REGEX.insert(0, (re.compile(r"^源码提交:(.+)$"), r"Source commit: \1"))
 
-TEXT.update({'本轮修正与检查限制': 'Corrections and review limits'})
-PHRASES.update({"Codex 独立复核": "Codex independent review", "见原始复核材料绑定记录": "see the binding in the original review evidence", "见原始复核材料": "see original review evidence"})
 
-TEXT.update({
-    "请求成功,并同时满足本例最低字符数、全部必需核对点、显著标记及结构要求。验证码或错误提示文字不单独决定任务是否通过。完整度、纯净度和 F1 另算,不作为通过门槛。": "The request must succeed and satisfy minimum length, every required checkpoint, significant marker, and structure requirement. Verification or error notices do not independently determine acceptance. Completeness, purity, and F1 are separate metrics, not pass thresholds.",
-})
 REGEX.insert(0, (re.compile(r"^(\d+) 个计划任务中的通过数 / 可判定数;每题等权,无法判定结果不参与分母。$"), r"Passed / decidable results among \1 planned tasks; each task has equal weight, and undecidable results are excluded from the denominator."))
 REGEX.insert(0, (re.compile('^每种方法均使用同一组 (\\d+) 个任务,包含通过、未通过、错误和超时。耗时是单次调用从输入到返回的时间,不包含调用之间的统一冷却间隔。每种方法内部按案例依次执行;方法之间的并发安排和限制见调用链检查。耗时仍受本地与远程环境、网络和产品内部限制影响。$'), 'Each method uses the same \\1 tasks, including passes, failures, errors, and timeouts. Latency runs from input to return and excludes the common cooldown between calls. Cases execute sequentially within each method; inter-method scheduling and limits are documented in the call-chain audit. Local and remote environments, networks, and product limits still affect latency.'))
 REGEX.insert(0, (re.compile(r"^(\d+)/(\d+) · 复核记录$"), r"\1/\2 · review records"))
 REGEX.insert(0, (re.compile(r"^(\d+) 条通过结果的参考词精确率低于 30%。此比例用于定位检查对象,不是统一失败线;通过不保证干净正文,正文参考的范围和时间也会影响该指标。$"), r"\1 passing results have reference word precision below 30%. This locates inspection candidates, not a universal failure threshold. Passing does not establish clean body text; reference scope and time also affect this metric."))
 
-TEXT.update({
-    "版本优先使用调用时记录的版本号;源码实现使用冻结的提交号。云端服务没有公开后端版本时如实标注。二进制哈希用于确认具体执行文件;事后核验的版本会说明依据。": "Versions use call-time records where available; source implementations use frozen commits. Undisclosed cloud backend versions are stated explicitly. Binary hashes identify the executable, and retrospectively verified versions state their evidence.",
-})
 
 def write_english_report(out: Path, *, source_routes=None) -> int:
     """Mirror Chinese HTML routes; a scoped refresh preserves other editions."""
diff --git a/harness/web_fetch/report_i18n_adoption_a.py b/harness/web_fetch/report_i18n_adoption_a.py
deleted file mode 100644
index 8bdbfd7ac..000000000
--- a/harness/web_fetch/report_i18n_adoption_a.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""English translations for the first adoption-page narrative shard."""
-
-TEXT = {
-    "Lexmount 两个区域、Firecrawl、Tavily、Exa、Cloudflare;通过各自 API 调用。":
-        "Lexmount in both regions, Firecrawl, Tavily, Exa, and Cloudflare; called through their respective APIs.",
-    "CowAgent、OpenClaw、DeepSeek Harness;按本轮保留的实现与配置执行。":
-        "CowAgent, OpenClaw, and DeepSeek Harness; run with the implementations and configurations retained for this run.",
-    "本地运行": "Local execution",
-    "LangChain WebBaseLoader、Crawl4AI、Moli WebFetch;使用本机网络与运行环境。":
-        "LangChain WebBaseLoader, Crawl4AI, and Moli WebFetch; using the local network and runtime environment.",
-    "内容选择": "Content selection",
-    "现有 PDF / Office 解析": "Existing PDF / Office parsing",
-    "Markdown 引用;无独立列表": "Markdown references; no separate list",
-    "页面超链接;非媒体接口": "Page hyperlinks; not a media interface",
-    "本端点只返回 Markdown": "This endpoint returns Markdown only",
-    "未声明 PDF / Office 正文解析": "PDF / Office body parsing not declared",
-    "页面引用;JSON HTML / eval 可取地址": "Page references; JSON HTML / eval can retrieve URLs",
-    "页面入口;HTML / eval 可取地址": "Page entries; HTML / eval can retrieve URLs",
-    "原始非 HTML 响应仅支持 html / json 模式":
-        "Raw non-HTML responses are supported only in html / json modes",
-    "支持 · 浏览器 + 等待参数": "Supported · Browser + wait parameters",
-    "URL 或 HTML 字符串;/content、/links、/screenshot 为其他端点。":
-        "URL or HTML string; /content, /links, and /screenshot are separate endpoints.",
-    "/markdown 返回字符串;AI /json 是独立端点。":
-        "/markdown returns a string; AI /json is a separate endpoint.",
-    "支持 cookies、HTTP authenticate 等参数;浏览器流量仍可能被识别和拦截。":
-        "Supports cookies, HTTP authenticate, and other parameters; browser traffic may still be detected and blocked.",
-    "无此能力 · HTTP,不运行 JS": "Not supported · HTTP, no JS execution",
-    "支持 · 浏览器 + JS 动作": "Supported · Browser + JS actions",
-    "无此能力 · 匿名 HTTP 获取": "Not supported · anonymous HTTP fetching",
-    "支持 · Contents 动态页抓取": "Supported · Contents dynamic-page crawling",
-    "支持 · 渲染与 Actions": "Supported · rendering and Actions",
-    "支持 · 按需渲染": "Supported · on-demand rendering",
-    "支持 · 默认运行 JS,支持多种就绪等待":
-        "Supported · runs JS by default and supports multiple readiness waits",
-    "JSON 返回最终 URL、状态、标题、响应头、重定向链和 HTML;--eval 可返回 DOM 字段。":
-        "JSON returns the final URL, status, title, response headers, redirect chain, and HTML; --eval can return DOM fields.",
-    "无此能力 · 需另用 Browser": "Not supported · requires a separate Browser",
-    "支持 · 复杂页建议 advanced": "Supported · advanced is recommended for complex pages",
-}
diff --git a/harness/web_fetch/report_i18n_adoption_b.py b/harness/web_fetch/report_i18n_adoption_b.py
deleted file mode 100644
index 1b977c0df..000000000
--- a/harness/web_fetch/report_i18n_adoption_b.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""English translations for the assigned adoption-report narrative segment."""
-
-TEXT = {
-
-    "页面中的链接可能被保留,不是媒体专用抽取保证。/pdf 是把网页打印成 PDF,不是读取已有 PDF;Workers AI 的 AI.toMarkdown 文档转换也不是本轮 /markdown。": "Links on the page may be retained, but this is not a dedicated media-extraction guarantee. /pdf prints a webpage to PDF rather than reading an existing PDF; Workers AI's AI.toMarkdown document conversion is also outside this run's /markdown endpoint.",
-    "只调用 /markdown,waitUntil=networkidle2;未提供源站登录 Cookie,未调用其他端点。": "Only /markdown was called with waitUntil=networkidle2; no source-site login Cookie was provided, and no other endpoint was called.",
-    "Cloudflare · Markdown 端点 ↗": "Cloudflare · Markdown endpoint ↗",
-    "CowAgent · WebFetch 源码 ↗": "CowAgent · WebFetch source ↗",
-    "正文使用原通用浏览器策略、无缓存;文件补测在调用前选择原生 HTTP 文件策略,读取 downloaded_files 并统一校验。未启用专用 PDF 策略、截图或 LLM 提取;媒体字段不代表内容理解。": "Body content used the original general browser strategy without caching; the supplemental file tests selected the native HTTP file strategy before each call, read downloaded_files, and applied uniform validation. The dedicated PDF strategy, screenshots, and LLM extraction were not enabled; media fields do not imply content understanding.",
-    "Crawl4AI · 链接与媒体 ↗": "Crawl4AI · Links and media ↗",
-    "Crawl4AI · PDF 策略 ↗": "Crawl4AI · PDF strategy ↗",
-    "Crawl4AI · 文件下载 ↗": "Crawl4AI · File downloads ↗",
-    "Crawl4AI · 配置 ↗": "Crawl4AI · Configuration ↗",
-    "DeepSeek Harness · MIME 策略 ↗": "DeepSeek Harness · MIME policy ↗",
-    "DeepSeek Harness · Markdown 转换 ↗": "DeepSeek Harness · Markdown conversion ↗",
-    "extras.links 和 imageLinks 是地址;PDF text 是解析后的文字,不是原 PDF。summary/highlights 与正文原文提取的语义不同,生成摘要不能作为独立 ground truth。": "extras.links and imageLinks are addresses; PDF text is parsed text, not the original PDF. summary/highlights have different semantics from verbatim body extraction, and generated summaries cannot serve as independent ground truth.",
-    "text=true、maxAgeHours=0,并取 links/imageLinks;本轮没有调用摘要或高亮生成。": "text=true and maxAgeHours=0, with links/imageLinks requested; this run did not invoke summary or highlight generation.",
-    "Exa · Contents 参数 ↗": "Exa · Contents parameters ↗",
-    "正文与资源题请求 markdown、links、images;原文件题只请求 rawBase64 并验收目标 URL 的解码字节。动态顶层选择器使用 Actions wait,随后执行预声明稳定延迟;选择器数量与 iframe 子文档条件仍标为未完整执行。未测试 audio、video、JSON、截图或 Parse。": "Body and resource tasks request markdown, links, and images; original-file tasks request rawBase64 alone and validate the decoded bytes for the target URL. Dynamic top-level selectors use an Actions wait followed by the predeclared settle delay; selector-count and iframe child-document conditions remain marked as not fully enforced. Audio, video, JSON, screenshots, and Parse were not tested.",
-    "Firecrawl · Scrape 格式 ↗": "Firecrawl · Scrape formats ↗",
-    "Firecrawl · 文档解析 ↗": "Firecrawl · Document parsing ↗",
-    "LangChain · WebBaseLoader 源码 ↗": "LangChain · WebBaseLoader source ↗",
-    "LangChain · 其他文档加载器 ↗": "LangChain · Other document loaders ↗",
-    "Lexmount · Extract 契约 ↗": "Lexmount · Extract contract ↗",
-    "Lexmount · WebFetch 概览 ↗": "Lexmount · WebFetch overview ↗",
-    "moli fetch;正文 markdown,原文件 html 字节输出": "moli fetch; markdown for body content and html byte output for original files",
-    "截图和 PDF 是启用 --layout 后生成的页面产物,不是读取已有文档或下载原媒体。官方未承诺文档 OCR、媒体理解或音频转写;非 HTML 响应不支持 Markdown。": "Screenshots and PDFs are page artifacts generated after enabling --layout, not reads of existing documents or downloads of original media. The official documentation does not promise document OCR, media understanding, or audio transcription; non-HTML responses do not support Markdown.",
-    "正文使用 Markdown,文件任务预先选择 html 原始字节模式;done + 私网屏蔽,无 layout、额外资源加载或 eval,无失败后格式回退。只有实际执行记录参与榜单。": "Body content used Markdown, while file tasks selected html raw-byte mode in advance; done + private-network blocking was used, without layout, additional resource loading, or eval, and without a post-failure format fallback. Only actual execution records enter the ranking.",
-    "Moli · 输出与接口边界 ↗": "Moli · Output and API boundaries ↗",
-    "OpenClaw · Web Fetch 文档 ↗": "OpenClaw · Web Fetch documentation ↗",
-    "OpenClaw · 抽取实现 ↗": "OpenClaw · Extraction implementation ↗",
-    "OpenClaw · HTML 转换器 ↗": "OpenClaw · HTML converter ↗",
-    "OpenClaw · Readability 插件 ↗": "OpenClaw · Readability plugin ↗",
-    "Tavily · Extract 契约 ↗": "Tavily · Extract contract ↗",
-    "Tavily · 提取深度 ↗": "Tavily · Extraction depth ↗",
-    "Tavily · Research 文件上传 ↗": "Tavily · Research file uploads ↗",
-    "按给定 URL 获取正文比较,不包含搜索、研究 Agent 或聊天模型。按页产品可以换算为每千页;浏览器时长产品必须带上耗时假设。套餐价、超额价与试用额度分开列示。": "The comparison fetches body content from the given URL and excludes search, research Agents, and chat models. Per-page products can be converted to cost per thousand pages; browser-time products must include a duration assumption. Plan prices, overage prices, and trial allowances are shown separately.",
-    "官方公开价格核验:2026-09-03;币种 USD,未加地区税费。以下为报价换算,不是本轮账单;供应商定义的提取成功不等于评测任务通过。": "Official public pricing verified: 2026-09-03; currency USD, excluding regional taxes. The figures below are pricing conversions, not this run's bill; provider-defined extraction success does not equal benchmark task acceptance.",
-    "本条官方资料核验:2026-09-11。": "Official sources for this entry verified: 2026-09-11.",
-    "下载价格与来源数据": "Download pricing and source data",
-    "标准抓取 1 credit / 页": "Standard extraction: 1 credit / page",
-    "月付满额 $0.749–3.8;年付满额约 $0.599–3.167": "Fully utilized monthly plans: $0.749–3.8; fully utilized annual plans: approximately $0.599–3.167",
-    "Hobby $19/月;年付 $190 起": "Hobby $19/month; annual billing starts at $190",
-    "套餐量越大单价越低;年付省 16.7%–20.0%;追加 $1–5/千积分": "Higher-volume plans have lower unit prices; annual billing saves 16.7%–20.0%; additional usage costs $1–5 per thousand credits",
-    "Basic 1 credit / 5 成功 URL;Advanced 为 2": "Basic: 1 credit / 5 successful URLs; Advanced: 2",
-    "Basic 按量 $1.6,套餐满额 $1–1.5;Advanced 翻倍": "Basic pay-as-you-go: $1.6; fully utilized plans: $1–1.5; Advanced costs twice as much",
-    "按量 $0.008/credit;月套餐 $30 起": "Pay-as-you-go: $0.008/credit; monthly plans start at $30",
-    "积分量大折扣;未找到公开年付优惠": "Volume discounts for credits; no public annual-billing discount was found",
-    "$1 / 千页 / 内容类型": "$1 / thousand pages / content type",
-    "Text $1;Text + Highlights $2;三种内容 $3": "Text $1; Text + Highlights $2; three content types $3",
-    "按量预付积分;无订阅或最低消费": "Prepaid pay-as-you-go credits; no subscription or minimum spend",
-    "企业量大折扣询价;未公布阶梯或年付折扣": "Contact sales for enterprise volume discounts; no tiers or annual-billing discounts are published",
-    "超额浏览器时间 $0.09/小时": "Additional browser time: $0.09/hour",
-    "无固定页价;5 秒/页时边际 $0.125/千页": "No fixed per-page price; at 5 seconds/page, the marginal cost is $0.125/thousand pages",
-    "Workers Paid $5/月起,含 10 浏览器小时": "Workers Paid starts at $5/month and includes 10 browser hours",
-    "Free 10 分钟/天;未公开时长量阶折扣": "Free: 10 minutes/day; no public volume discount for duration",
-    "套餐折算价=套餐费用 ÷ 可用积分 × 每页积分 × 1,000。只用完 20% 的额度,实际平均单价就是满额折算价的 5 倍。超额单价用于追加用量,不能替代套餐平均价。免费额度内供应商费用可为零;超额费用及自身运行成本另计。": "Converted plan price = plan cost ÷ available credits × credits per page × 1,000. If only 20% of the allowance is used, the actual average unit price is 5 times the fully utilized price. Overage pricing applies to additional usage and cannot replace the average plan price. Provider cost may be zero within the free allowance; overage charges and self-hosting costs are separate.",
-    "Firecrawl · Scrape · 月套餐 + 年付优惠 + $5 批次追加": "Firecrawl · Scrape · Monthly plans + annual-billing discounts + $5 batch add-ons",
-    "价格与账期明细 ↗": "Pricing and billing details ↗",
-    "定价页与扣费 FAQ ↗": "Pricing page and billing FAQ ↗",
-    "详细扣费与结转 ↗": "Detailed billing and rollover ↗",
-    "增强代理 ↗": "Enhanced proxy ↗",
-    "Scrape 参数与原文件 ↗": "Scrape parameters and original files ↗",
-    "$19/月": "$19/month",
-    "$190/年;月均 $15.833(较月付省 16.7%)": "$190/year; monthly average $15.833 (16.7% less than monthly billing)",
-    "$5 / 1,000 credits;即 $5/千标准页": "$5 / 1,000 credits; equivalent to $5/thousand standard pages",
-    "$99/月": "$99/month",
-    "$990/年;月均 $82.5(较月付省 16.7%)": "$990/year; monthly average $82.5 (16.7% less than monthly billing)",
-    "$5 / 2,000 credits;即 $2.5/千标准页": "$5 / 2,000 credits; equivalent to $2.5/thousand standard pages",
-    "$399/月": "$399/month",
-    "$3,990/年;月均 $332.5(较月付省 16.7%)": "$3,990/year; monthly average $332.5 (16.7% less than monthly billing)",
-    "$5 / 2,500 credits;即 $2/千标准页": "$5 / 2,500 credits; equivalent to $2/thousand standard pages",
-    "$749/月": "$749/month",
-    "$7,190/年;月均 $599.167(较月付省 20.0%)": "$7,190/year; monthly average $599.167 (20.0% less than monthly billing)",
-    "$5 / 5,000 credits;即 $1/千标准页": "$5 / 5,000 credits; equivalent to $1/thousand standard pages",
-    "功能": "Features",
-    "计费": "Billing",
-}
diff --git a/harness/web_fetch/report_i18n_adoption_c.py b/harness/web_fetch/report_i18n_adoption_c.py
deleted file mode 100644
index 02ffb50c8..000000000
--- a/harness/web_fetch/report_i18n_adoption_c.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""English translations for adoption-report narrative nodes, shard C."""
-
-TEXT = {
-    "边界": "Boundary",
-    "基础 Scrape / auto / enhanced": "Base Scrape / auto / enhanced",
-    "1 credit/页": "1 credit/page",
-    "JSON 模型抽取": "JSON model extraction",
-    "+4 credits/页": "+4 credits/page",
-    "普通网页总计 5;不能按基础单价预算": "5 total for a standard webpage; the base rate alone is not a valid budget",
-    "提示注入检查 / ZDR": "Prompt-injection checks / ZDR",
-    "分别 +4 / +1 credits/页": "+4 / +1 credits/page, respectively",
-    "PDF 文字解析": "PDF text parsing",
-    "基础价之上 +1 credit/PDF 页": "+1 credit/PDF page on top of the base price",
-    "Interact 浏览器交互": "Interact browser automation",
-    "2–7 credits/分钟,至少 1 分钟": "2–7 credits/minute, with a 1-minute minimum",
-    "纯代码 2;使用 prompt 7;不属于 Scrape 模式": "2 for code only; 7 when using a prompt; outside Scrape mode",
-    "30 credits/请求;JSON 为 34": "30 credits/request; 34 for JSON",
-    "特殊域名走 Grok,非标准页价": "Special domains use Grok and are not charged at the standard per-page rate",
-    "免费额度": "Free credits",
-    "年付与用量折扣": "Annual billing and volume discounts",
-    "追加与并发": "Additional credits and concurrency",
-    "失败扣费": "Charges for failures",
-    "额度结转": "Credit rollover",
-    "其他档位": "Other tiers",
-    "创业公司优惠": "Startup discount",
-    ":年付套餐积分加倍一年;融资少于 $10M、少于 50 人、成立未满五年,既有客户也可申请。赠送价值上限 $2,500/月、$30,000/年。若满额使用且获批,套餐折算单价再减半,不意味着追加积分也打五折。": ": Annual-plan credits are doubled for one year; companies with less than $10M in funding, fewer than 50 employees, and less than five years in operation may apply, including existing customers. The benefit is capped at $2,500/month and $30,000/year. If approved and fully used, the plan's effective unit price is halved again; this does not mean additional credits are also discounted by half.",
-    "学生计划": "Student plan",
-    ":合资格学生使用学校邮箱申请 10,000 免费积分,用于教育或个人项目;不是已确认的每月赠送。优惠是否可叠加不作假设。": ": Eligible students can apply with a school email for 10,000 free credits for educational or personal projects; this is not confirmed as a monthly grant. No assumption is made that discounts can be combined.",
-    "Tavily · Extract · 按成功 URL 累积扣积分": "Tavily · Extract · Credits accrue by successful URL",
-    "本轮使用 Basic Extract。套餐表按 Basic 满额换算;Advanced 用量相同则积分翻倍、每千页价格翻倍,不能把搜索的一次调用价格套给 Extract。": "This run uses Basic Extract. Plan conversions assume full use for Basic; Advanced consumes twice the credits for the same volume and doubles the price per thousand pages. Per-call Search pricing does not apply to Extract.",
-    "积分单价与套餐 ↗": "Credit rates and plans ↗",
-    "Extract 用量说明 ↗": "Extract usage details ↗",
-    "$30/月": "$30/month",
-    "未公开年付优惠": "No annual billing discount is publicly disclosed",
-    "按量 $0.008/credit": "Pay as you go at $0.008/credit",
-    "$100/月": "$100/month",
-    "$220/月": "$220/month",
-    "$500/月": "$500/month",
-    "模式": "Mode",
-    "每千成功页积分": "Credits per thousand successful pages",
-    "按量每千页": "Pay-as-you-go price per thousand pages",
-    "月套餐满额每千页": "Price per thousand pages at full monthly-plan utilization",
-    "Basic(本轮)": "Basic (this run)",
-    "扣费与批量调用": "Charging and batch requests",
-    "优惠与额度边界": "Discount and credit boundaries",
-    "Exa · Contents · 正文、摘录、摘要分别计费": "Exa · Contents · Text, highlights, and summaries are billed separately",
-    "本轮是给定 URL 的 Contents,text=true,不是 Search。每页每种内容类型单独计费,不能把一次批量请求当成一页;链接和图片地址不等于下载原文件。": "This run uses Contents for specified URLs with text=true, not Search. Each content type is billed separately per page; one batch request cannot be counted as one page. Link and image addresses are not downloaded original files.",
-    "Contents 单价与计费单位 ↗": "Contents rates and billing units ↗",
-    "免费额度与方案 ↗": "Free credits and plans ↗",
-    "充值与账单 ↗": "Top-ups and billing ↗",
-    "逐 URL 状态与 costDollars ↗": "Per-URL status and costDollars ↗",
-    "返回内容": "Returned content",
-    "每千页价格": "Price per thousand pages",
-    "条件": "Condition",
-    "Text(本轮正文)": "Text (body text in this run)",
-    "1 种内容": "1 content type",
-    "Text + Highlights 或 Summary": "Text + Highlights or Summary",
-    "2 种内容;Summary 不再重复加一遍": "2 content types; Summary is not charged again",
-    "3 种内容": "3 content types",
-    "询价": "Contact sales",
-    "量大折扣与 SLA;无公开阶梯": "Volume discounts and SLA; no public tiers",
-    "免费与充值": "Free credits and top-ups",
-    "所查普通 API Key 计费文档没有明确的所有失败均免费的承诺;应核验逐 URL 状态与 costDollars。x402 按请求参数计算付款,是不同支付方式,不能套用为本轮的结算规则。": "The reviewed standard API Key billing documentation does not explicitly promise that every failure is free; per-URL status and costDollars should be checked. x402 calculates payment from request parameters and is a different payment method, so its settlement rules do not apply to this run.",
-    "价格范围": "Pricing scope",
-    "公开定价按内容类型分别收费。泛称“包含内容”的搜索描述不意味着独立 Contents 免费;年付折扣、免费积分结转及本轮 extras 字段的独立加价未获公开确认。": "Public pricing charges separately by content type. A general Search description that says content is included does not mean standalone Contents is free; annual billing discounts, rollover of free credits, and separate surcharges for the extras fields used in this run are not publicly confirmed.",
-    "Cloudflare · Markdown · 按浏览器时间,不按请求数": "Cloudflare · Markdown · Billed by browser time, not request count",
-    "官方现称 Browser Run。本轮 /markdown 属于 Quick Actions,只收浏览器时长费用,不收 Browser Sessions 的并发费用;不是 Workers AI toMarkdown 或 Markdown for Agents。": "The official name is now Browser Run. The /markdown endpoint used in this run is a Quick Action and incurs browser-duration charges, not Browser Sessions concurrency charges; it is neither Workers AI toMarkdown nor Markdown for Agents.",
-    "Browser Run 定价与取整 ↗": "Browser Run pricing and rounding ↗",
-    "Workers 基础月费 ↗": "Workers base monthly fee ↗",
-    "Markdown 接口 ↗": "Markdown endpoint ↗",
-    "免费限额 ↗": "Free limits ↗",
-    "方案": "Plan",
-    "包含浏览器用量": "Included browser usage",
-    "超额计价": "Overage pricing",
-    "10 分钟/天": "10 minutes/day",
-    "日额度耗尽后限流,不是可累积的月额度": "Rate-limited after the daily allowance is exhausted; this is not a cumulative monthly allowance",
-    "10 小时/月;各浏览器方法共用": "10 hours/month, shared across browser methods",
-    "$0.09/小时;另有 $5/月 Workers 基础费用": "$0.09/hour, plus a $5/month Workers base fee",
-    "计量": "Metering",
-    "失败边界": "Failure boundaries",
-    "折扣与附加成本": "Discounts and additional costs",
-}
diff --git a/harness/web_fetch/report_i18n_adoption_d.py b/harness/web_fetch/report_i18n_adoption_d.py
deleted file mode 100644
index 6b53342b3..000000000
--- a/harness/web_fetch/report_i18n_adoption_d.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""English translations for the adoption report, shard D."""
-
-TEXT = {
-    "本地浏览器;无托管调用单价": "Local browser; no managed per-call price",
-    "本地 CLI;无托管 API 单价": "Local CLI; no managed API unit price",
-    "本地开源库;非云套餐": "Local open-source library; no cloud plan",
-    "计算、浏览器、网络及代理成本自担;商业云服务价格不能当成本地调用的费用。": "The caller pays compute, browser, network, and proxy costs. Commercial cloud prices do not represent local-call costs.",
-    "内置或本地实现": "Built-in or local implementation",
-    "接口与实现 ↗": "Interface and implementation ↗",
-    "实际版本和接口见运行记录;下方链接指向官方说明。": "See the run records for the actual version and interface. The links below point to official documentation.",
-    "使用本轮绑定提交构建的二进制;提交、构建环境与 SHA-256 见环境与复现。": "Use the binary built from this run's bound commit. See Reproduction for the commit, build environment, and SHA-256.",
-    "假设仅使用 /markdown,全部用量可计费;按响应头 X-Browser-Ms-Used 计时。每千页边际价=每页秒数 × $0.025,不含最低月费、赠送时长和月度取整。": "Assumes exclusive use of /markdown and that all usage is billable, timed by the X-Browser-Ms-Used response header. Marginal price per thousand pages = seconds per page × $0.025, excluding the monthly minimum, included duration, and monthly rounding.",
-    "5 秒": "5 seconds",
-    "10 秒": "10 seconds",
-    "40 秒": "40 seconds",
-    "月支出=$5+$0.09 × max(0, round(月浏览器小时数)−10),半小时向上取整。仅含 Workers 基础月费与浏览器时长;已有付费 Workers 时,增量成本不应重复加 $5。不是用榜单的客户端耗时推算账单。": "Monthly spend = $5 + $0.09 × max(0, round(monthly browser hours) − 10), with half-hours rounded up. This includes only the Workers base monthly fee and browser duration; if paid Workers is already in use, the incremental cost should not add the $5 again. The bill is not estimated from the leaderboard's client-side latency.",
-    "未查到公开价格": "No public pricing found",
-    "官方 WebFetch 文档 ↗": "Official WebFetch documentation ↗",
-    "本轮本地 CLI;无托管 API 单价": "Local CLI in this run; no hosted API unit price",
-    "官方项目说明 ↗": "Official project description ↗",
-    "本轮本地开源库;非云套餐": "Local open-source library in this run; not a cloud plan",
-    "计算、浏览器、网络及代理成本自担;商业云服务价格不能当成本轮本地调用的费用。": "Compute, browser, network, and proxy costs are borne by the user; commercial cloud-service pricing cannot be treated as the cost of this run's local calls.",
-    "官方文档 ↗": "Official documentation ↗",
-    "本轮内置或本地实现": "Built-in or local implementation in this run",
-    "本轮接口与实现 ↗": "API and implementation used in this run ↗",
-    "对定价设计的启示": "Implications for pricing design",
-    "建议以“成功取得一个 URL 的内容”为基础计费单位,前台显示每千页价格;后台可以用积分记账,但要公开各能力的换算。不按 HTTP 请求次数简单收费:一次批量调用可能含多个 URL,一次取数也可能经历重试。": "Use ‘successfully retrieving content for one URL’ as the basic billing unit and show the price per thousand pages to users. Credits may be used for internal accounting, but the conversion for each capability should be public. Do not charge simply by HTTP request count: one batch call may contain multiple URLs, and one retrieval may involve retries.",
-    "基础取数与增强能力分开": "Separate basic retrieval from enhanced capabilities",
-    "普通正文是一档;高成本代理、长时间交互、OCR、模型结构化抽取单列加价。不能只因网站执行了 JavaScript 就加价,应由真实资源成本和服务承诺决定,并给用户加价上限。": "Price ordinary body text as one tier, with separate surcharges for high-cost proxies, long-running interactions, OCR, and model-based structured extraction. Do not charge extra merely because a site executes JavaScript; surcharges should reflect actual resource costs and service commitments, with a user-visible cap.",
-    "先按量,再按承诺量折扣": "Start with usage-based pricing, then discount committed volume",
-    "低门槛按量价便于试用;月套餐降低稳定用量的单价,年付再交换资金与用量承诺。公布超额价、额度有效期、并发上限和自动续费上限,避免套餐便宜、追加昂贵却看不出来。": "Low-entry usage-based pricing makes trials easier. Monthly plans reduce the unit price for steady usage, while annual payment trades funding and usage commitments for further discounts. Publish overage prices, credit validity, concurrency limits, and automatic-renewal caps so inexpensive plans do not conceal costly additional usage.",
-    "把“成功”写成可执行规则": "Define ‘success’ as an enforceable rule",
-    "建议网络错误、空内容、可识别的登录或验证码页不收基础提取费;服务内部重试不重复计页。同一 URL 的再次主动刷新可计新页。成功取回内容不承诺每项业务判断正确,边界需写入接口契约。": "Do not charge the basic extraction fee for network errors, empty content, or recognizable login or CAPTCHA pages, and do not count internal service retries as additional pages. A new user-initiated refresh of the same URL may count as a new page. Successfully retrieving content does not guarantee that every business judgment is correct; this boundary should be stated in the API contract.",
-    "价格区间不能代替成本测算": "Price ranges cannot replace cost modeling",
-    "用代理、浏览器时间、带宽、解析、失败重试和支持成本测算每千个可计费页面的成本,再用目标毛利反推售价。公开竞品价格是参照,不是成本下限;本报告没有完整账单,不能直接给出“最便宜且最好”的排名。": "Model the cost per thousand billable pages using proxy, browser-time, bandwidth, parsing, failed-retry, and support costs, then derive the selling price from the target gross margin. Public competitor pricing is a reference, not a cost floor. This report does not have complete billing data and cannot directly rank a provider as the ‘cheapest and best.’",
-    "用于报价的成本公式与比较边界": "Cost formula and comparison boundaries for quoting",
-    "单位成本=该档全部请求的资源与运营成本 ÷ 可计费成功页数;价格底线=单位成本 ÷(1−目标毛利率)。即使失败不向客户收费,失败重试成本仍须纳入分子。套餐定价还需考虑未用额度、峰值并发及承诺服务。": "Unit cost = resource and operating costs for all requests in the tier ÷ billable successful pages; price floor = unit cost ÷ (1 − target gross margin). Even when customers are not charged for failures, failed-retry costs must remain in the numerator. Plan pricing must also account for unused credits, peak concurrency, and committed service levels.",
-    "比较实际采购成本时,用实际总账单 ÷ 满足相同业务验收条件的页面数。不要直接把当前公开价格除以本轮任务通过率:执行日期、付费档位、供应商成功口径、缓存与功能加价都未必一致。以上为定价设计建议,不是提供方现行条款。": "To compare actual procurement costs, use the actual total bill ÷ pages meeting the same business acceptance criteria. Do not simply divide current public pricing by this run's task pass rate: execution dates, paid tiers, provider definitions of success, caching, and feature surcharges may differ. These are pricing-design recommendations, not current provider terms.",
-    "本表指标说明": "Metric definitions for this table",
-    "Moli 一次性 fetch,默认运行网页 JavaScript 并输出 Markdown;不调用模型。": "Moli performs a one-shot fetch, runs webpage JavaScript by default, and outputs Markdown; it does not call a model.",
-    "stdout Markdown;stderr 独立诊断": "Markdown on stdout; separate diagnostics on stderr",
-    "每次启动独立进程;wait-until=done": "Starts a separate process for each call; wait-until=done",
-}
diff --git a/harness/web_fetch/report_interfaces.py b/harness/web_fetch/report_interfaces.py
index 1673eebf6..f8c843be3 100644
--- a/harness/web_fetch/report_interfaces.py
+++ b/harness/web_fetch/report_interfaces.py
@@ -21,19 +21,19 @@ def method_provenance(out):
 
 
 def method_spec(name, record, *, timeout_seconds=None, language="zh"):
- def t(zh,en):return zh if language=="zh" else en
+ bound=browser_method_registry().get(name,{}).get('report_interface')
+ if bound is not None:
+  return bound[language] if set(bound)=={'zh','en'} else bound
  catalog=report_method_catalog() if language=='zh' else report_method_catalog(language)
  owner=catalog['adapters'].get((record or {}).get('provider_type'),{}).get('interface_method')
  registered=catalog['methods'][owner] if owner else report_method(name,record,language=language)
- base=registered.get('interface') or {
-  'entry':t('未登记','Not registered'),'auth':t('未登记','Not registered'),'params':[],
-  'fields':[],'request':{},'source':t('未登记','Not registered'),
-  'notes':t('接口资料尚未登记;请查阅保存的调用证据。','Interface metadata has not been registered; consult saved call evidence.')}
+ messages=catalog['messages']
+ base=registered.get('interface') or catalog['unregistered_interface']
  meta=(record or {}).get('provider_metadata',{})
  if registered.get('runtime_parameters'):
-  budget=(t(f'{timeout_seconds:g} 秒外层截止',f'{timeout_seconds:g}-second outer deadline')
+  budget=(messages['outer_deadline'].format(timeout=f'{timeout_seconds:g}')
           if positive_number(timeout_seconds)
-          else t('外层截止时间未记录','Outer deadline not recorded'))
+          else messages['outer_deadline_unrecorded'])
   context={**meta,'report_outer_budget':budget}
   params=list(base['params']);notes=base['notes'];request=dict(base['request'])
   for parameter in registered['runtime_parameters']:
@@ -65,17 +65,13 @@ def t(zh,en):return zh if language=="zh" else en
 def _observed_wait(meta, *, language="zh"):
  navigation=meta.get('navigation_wait_until')
  completion=meta.get('wait_until')
- if language=='en':
-  if navigation and completion and navigation!=completion:
-   return f'Navigation wait {navigation}; output completion {completion}'
-  if navigation:return f'Navigation wait {navigation}'
-  if completion:return f'Output completion {completion}'
-  return 'Call records do not provide a verifiable wait condition'
+ messages=report_method_catalog(language)['messages']
  if navigation and completion and navigation!=completion:
-  return f'导航等待 {navigation};输出完成条件 {completion}'
- if navigation:return f'导航等待 {navigation}'
- if completion:return f'输出完成条件 {completion}'
- return '调用记录未提供可核对的等待条件'
+  return messages['wait_both'].format(navigation=navigation,completion=completion)
+ if navigation:return messages['wait_navigation'].format(navigation=navigation)
+ if completion:return messages['wait_completion'].format(completion=completion)
+ return messages['wait_unrecorded']
+
 
 
 def _route_template(rule, metadata):
@@ -100,38 +96,39 @@ def _route_template(rule, metadata):
 
 def _observed_route(engine, meta, *, language="zh", compact=False):
  """Describe only the registered interface persisted by one measured call."""
- def t(zh,en):return zh if language=='zh' else en
+ messages=report_method_catalog(language)['messages']
  route=meta.get('route')
  extractor=meta.get('extractor')
  contract=meta.get('output_contract')
- route_note=t(f'(route={route})',f' (route={route})') if route and not compact else ''
+ route_note=messages['route_note'].format(route=route) if route and not compact else ''
  for rule in report_method(engine,language=language).get('observed_routes',[]):
   description=_route_template(rule,meta)
   if description is not None:
    return rule.get('compact_text',description) if compact else description+route_note
  if contract=='original_file_bytes':
-  source=(t('调用记录 extractor=','Recorded extractor=')+str(extractor) if extractor
-          else t('调用记录未注明原始文件接口','Original-file interface not recorded'))
-  return source+(t(' → 文件字节',' → file bytes') if compact else t(' → 原始文件字节',' → original file bytes'))+route_note
+  source=(messages['extractor_prefix']+str(extractor) if extractor
+          else messages['original_file_unrecorded'])
+  return source+(messages['file_bytes'] if compact else messages['original_file_bytes'])+route_note
  fields=[]
  for key in ('route','extractor','output_contract','dump'):
   if meta.get(key):fields.append(f'{key}={meta[key]}')
  if fields:
-  return t('调用记录:','Call record: ')+t(';','; ').join(fields)+t(';未据此补写未记录的转换步骤','; unrecorded conversion steps are not inferred')
- if compact:return t('接口未记录','Interface not recorded')
- return t('调用记录未提供可核对的接口路线;不推断原生 Markdown、HTML 或额外转换','Call records do not provide a verifiable route; native Markdown, HTML or additional conversion is not inferred')
+  return messages['call_prefix']+messages['separator'].join(fields)+messages['conversion_unrecorded']
+ if compact:return messages['interface_unrecorded']
+ return messages['route_unrecorded']
 
 
 def browser_task_interface_matrix(cases, providers, records=None, *, compact=False, language="zh"):
  """Describe task routes, preferring persisted call metadata when available."""
  from collections import Counter
- def t(zh,en):return zh if language=='zh' else en
- methods=(report_method_catalog() if language=='zh' else report_method_catalog(language))['methods']
+ catalog=report_method_catalog(language)
+ messages=catalog['messages']
+ methods=catalog['methods']
  selected={name:next((row for row in (records or []) if row['provider']==name),None)
            for name in providers}
  engines={name:native_browser_engine(name,selected[name]) for name in providers}
  routes={name:item.get('routes',{}) for name,item in methods.items()}
- capabilities={name:item.get('capabilities','Not registered') for name,item in routes.items()}
+ capabilities={name:item.get('capabilities',messages['not_registered']) for name,item in routes.items()}
  tracks=[]
  from .models import REPORT_FAMILIES, report_family
  for case in cases.values():
@@ -148,7 +145,7 @@ def t(zh,en):return zh if language=='zh' else en
   for provider in providers:
    engine=engines[provider]
    if engine not in methods:
-    cells[provider]=[{'mode':'unregistered','count':len(group),'description':t('未登记','Not registered'),'wait':'Not recorded'}]
+    cells[provider]=[{'mode':'unregistered','count':len(group),'description':messages['not_registered'],'wait':messages['not_recorded']}]
     continue
    parts=[];counts=Counter()
    for case in group:
@@ -173,8 +170,8 @@ def t(zh,en):return zh if language=='zh' else en
      description=plan['description'].format(**routes.get(engine,{}))
      wait=plan['wait'].format(**routes.get(engine,{}))
     else:
-     description=t('未登记','Not registered')
-     wait='Not recorded'
+     description=messages['not_registered']
+     wait=messages['not_recorded']
     counts[(mode,description,wait)]+=1
    for (mode,description,wait),count in counts.items():
     parts.append({'count':count,'mode':mode,'description':description,'wait':wait})
@@ -189,7 +186,7 @@ def t(zh,en):return zh if language=='zh' else en
     parts=list(grouped.values())
    cells[provider]=parts
   rows.append({'track':track,'count':len(group),'cells':cells})
- selected_capabilities={name:capabilities.get(engines[name],t('未登记','Not registered')) for name in providers}
+ selected_capabilities={name:capabilities.get(engines[name],messages['not_registered']) for name in providers}
  if records is not None:
   for provider in providers:
    engine=engines[provider]
@@ -198,12 +195,14 @@ def t(zh,en):return zh if language=='zh' else en
     if row['provider']!=provider:continue
     description=_observed_route(engine,row.get('provider_metadata') or {},language=language)
     if description not in observed:observed.append(description)
-   selected_capabilities[provider]=(t('本轮调用记录:','Recorded calls: ')+t(';','; ').join(observed)
-     if observed else t('本轮没有可核对的调用接口记录。','No verifiable call interfaces were recorded.'))
+   selected_capabilities[provider]=(messages['calls_prefix']+messages['separator'].join(observed)
+     if observed else messages['calls_unrecorded'])
  return {'providers':providers,'capabilities':selected_capabilities,'rows':rows}
 
 
 def render_interfaces(out,records):
+ messages=report_method_catalog()['messages']
+ english_messages=report_method_catalog('en')['messages']
  from .artifacts import record_public_call_log_bindings, verify_public_call_log
  dataset_path=out/"dataset.json"
  dataset=json.loads(dataset_path.read_text()) if dataset_path.is_file() else {}
@@ -216,9 +215,9 @@ def fields(rows,english):
 
  measured={r['provider'] for r in records}
  names=sorted(set(SPECS)|measured,key=lambda name:(name not in measured,name))
- body='

按本轮适配器介绍,不混用完整产品能力。输入示例展示正文配置,资源配置差异单列;它们不是每条历史请求的逐字回放。各调用所属批次及实际配置见“环境与复现”。输出示例取存档调用,经适配器整理后的真实节选,不冒充原始 HTTP 响应。Key 和账号均用占位符。

' + body=messages['interface_intro_html'] if native_default: - body='

已测方法的输入展示保存的公开调用字段,输出展示同一次调用的实际节选。完整配置、原始响应和日志见环境与调用详情。未参评方法保留仓库登记示例。

' + body=messages['interface_measured_intro_html'] case_order={case['id']: index for index, case in enumerate(dataset.get('cases', []))} for i,name in enumerate(names): r=min((r for r in records if r['provider']==name), key=lambda row: case_order.get(row['case_id'], len(case_order)), default=None) @@ -230,26 +229,26 @@ def fields(rows,english): if bindings: request=json.loads(verify_public_call_log(out,bindings[-1]).read_text())["request"] model=method_page_model(name,name,[x for x in records if x["provider"]==name]) - s={**s,"entry":model["endpoint"],"params":[],"request":request,"notes":"此处展示该次公开调用记录保存的请求字段;完整配置和日志见环境与调用详情。产品内部超时以冻结配置和实际请求为准。"} + s={**s,"entry":model["endpoint"],"params":[],"request":request,"notes":messages['interface_request_note']} english_model=method_page_model(name,name,[x for x in records if x["provider"]==name],language="en") - english={**english,"entry":english_model["endpoint"],"params":[],"request":request,"notes":"These are the request fields saved in this public call record. Full configuration and logs are in the environment and call details; internal product timeouts follow the frozen configuration and actual request."} + english={**english,"entry":english_model["endpoint"],"params":[],"request":request,"notes":english_messages['interface_request_note']} access=method_access(name,records) english_access=method_access(name,records,language="en") if r: content=contained_path(out,r['artifact_path']).read_text();sample={k:r.get(k) for k in ['ok','status_code','title','final_url']};sample['content_excerpt']=content[:450];sample['content_characters']=len(content) - else:sample={'measurement':None,'note':'仓库登记接口,未纳入本轮;此处不提供虚构的运行输出。'} + else:sample={'measurement':None,'note':messages['interface_unmeasured_sample']} # Explicit whitelist: no local paths, request IDs, credentials or session metadata. meta=r.get('provider_metadata',{}) if r else {};sample['metadata']={k:meta[k] for k in ['engine','upstream_status','links_count','images_count','extractor','truncated','document_count'] if k in meta} dest=out/'adoption'/'interfaces';dest.mkdir(exist_ok=True);(dest/(name+'.json')).write_text(json.dumps({'interface':s,'access':access,'sample':sample,'attempt_id':r['attempt_id'] if r else None},ensure_ascii=False,indent=2)+'\n') body+=f'
'+bilingual_text(browser_method_label(name),browser_method_label(name,language='en'))+'' body+='

'+bilingual_text(s['entry'],english['entry'])+'

'+bilingual_text(s['auth'],english['auth'])+'

' if access: - body+='

账号与获取入口

'+bilingual_text(access['account'],english_access['account'])+'

'+bilingual_text(access['configuration'],english_access['configuration'])+'

'+ ' · '.join(''+bilingual_text(label,english_access['links'][index][0])+'' for index,(label,url) in enumerate(access['links']))+'

实际版本和接口见运行记录;下方链接指向官方说明。

' + body+=messages['interface_accounts_html']+bilingual_text(access['account'],english_access['account'])+'

'+bilingual_text(access['configuration'],english_access['configuration'])+'

'+ ' · '.join(''+bilingual_text(label,english_access['links'][index][0])+'' for index,(label,url) in enumerate(access['links']))+messages['interface_source_note_html'] if access and access.get('env_vars'): body+='

'+', '.join(''+escape(value)+'' for value in access['env_vars'])+'

' - sample_label='本轮输出节选 · 统一记录' if r else '本轮未执行' - body+='

输入

'+fields(s['params'],english['params'])+('

本轮保存的调用字段

' if bindings else '

调用输入示例

')+pre(s['request'])+'

输出

'+fields(s['fields'],english['fields'])+'

'+sample_label+'

'+pre(sample)+'
' - measured_link='该次完整输出与判定 →' if r else '未纳入本轮;参数说明为仓库登记模式,不计入本轮结果' - example_note='' if bindings else bilingual_text('以下接口说明与数值属于仓库登记示例,不是这次调用的请求记录。', 'The following interface descriptions and values are registered examples, not this call’s request record.') - body+='

'+example_note+bilingual_text(s['notes'],english['notes'])+'

核对实现:'+escape(s['source'])+' · '+measured_link+' · 示例 JSON

' + sample_label=messages['interface_sample_label'] if r else messages['interface_not_run_label'] + body+=messages['interface_input_card_html']+fields(s['params'],english['params'])+(messages['interface_saved_fields_html'] if bindings else messages['interface_input_example_html'])+pre(s['request'])+messages['interface_output_card_html']+fields(s['fields'],english['fields'])+'

'+sample_label+'

'+pre(sample)+'' + measured_link='" + esc(fingerprint) + "") + cell(str(count))) + for name, counts in sorted(inventories.items()) + for fingerprint, count in counts.items()] + explanation = model_text( + "表中列出每个方法采用的结果数及其版本依据。unknown 表示未记录版本;reported-version 表示仅记录了版本名称,未核验二进制。多个版本合并的累计成绩不代表某一个版本独立跑完整套任务。", + "Every selected result is counted below. unknown means no version was recorded; reported-version identifies a recorded version label without a verified binary. When several builds are combined, the cumulative score is not a complete run by any single build.") + return section(model_text("局部重跑与结果来源", "Partial reruns and result provenance"), + "

" + explanation + "

" + table( + [model_text("方法", "Method"), model_text("版本依据", "Version evidence"), model_text("采用结果数", "Selected results")], + rows, metrics=[]), "implementation-scope") + + def render_methods(out,records,*,hero,section,table,cell,link,esc,authored=True): if records and all(row.get('provider_type') == 'fixture' for row in records): body = hero('合成示例适配器', '离线检查安装、评分、证据和报告导航。') + section( @@ -45,7 +64,7 @@ def render_methods(out,records,*,hero,section,table,cell,link,esc,authored=True) '

fetch(EvalCase, timeout) → ProviderResult

' '

输入为任务定义与超时预算,输出为预设的内容或错误。完整输出见该方法的结果详情。

', 'interface-' + name) - return body + return body + _implementation_scope(records, section=section, table=table, cell=cell, esc=esc) dataset=json.loads((out/'dataset.json').read_text()) if (out/'dataset.json').is_file() else {} native=measurement_presentation(dataset).native_outputs if not authored and not native: @@ -69,7 +88,7 @@ def render_methods(out,records,*,hero,section,table,cell,link,esc,authored=True) 'interface-'+name) for anchor in sorted(anchors): body+=section('费用信息','

本轮结果未绑定价格资料;费用取决于调用方配置与服务账单。

',anchor) - return body + return body + _implementation_scope(records, section=section, table=table, cell=cell, esc=esc) source=Path(__file__).resolve().parents[2]/'research/provider-adoption.json' data=json.loads(source.read_text());names=sorted({r['provider'] for r in records}) counts=adoption_counts(data,sorted(set(PROFILES)|set(names))) @@ -126,4 +145,4 @@ def render_methods(out,records,*,hero,section,table,cell,link,esc,authored=True) ('interfaces','接口与示例',render_interfaces(out,records)), ('scope','统计口径',scope), ])) - return body + return body + _implementation_scope(records, section=section, table=table, cell=cell, esc=esc) diff --git a/harness/web_fetch/report_package.py b/harness/web_fetch/report_package.py index 390611ad8..2fd9b22eb 100644 --- a/harness/web_fetch/report_package.py +++ b/harness/web_fetch/report_package.py @@ -28,13 +28,24 @@ def _require(condition, detail): raise ValueError(str(detail)) -def validate_release(source: Path, *, presentation_binding: Path | None = None) -> bool: +def validate_release(source: Path, *, presentation_binding: Path | None = None, snapshot_audit=False) -> bool: + manifest_path=source/'report-manifest.json' + audit_state=manifest_path.is_file() and json.loads(manifest_path.read_text()).get('evidence_state')=='published_snapshot_score_audit' + if snapshot_audit: + if not audit_state or presentation_binding is not None: + raise ValueError('Snapshot packaging requires its explicit diagnostic report') + from .published_snapshot import check_snapshot_audit + check_snapshot_audit(source) + return False + if audit_state: + raise ValueError('Published snapshot audits require --snapshot-audit; they are not certified releases') if presentation_binding is not None: from .report_build import validate_local_presentation validate_local_presentation(source, presentation_binding) return False dataset_path = source / "dataset.json" dataset = json.loads(dataset_path.read_text()) if dataset_path.exists() else {} + records = load_jsonl(source / "results.jsonl") from .reference_lifecycle import require_certifiable_evidence manifests = [] for name in ('manifest.json', 'execution.json'): @@ -47,7 +58,7 @@ def validate_release(source: Path, *, presentation_binding: Path | None = None) if content_binding: from .report_explainer import current_browser_article_model current_browser_article_model(source, {case['id']:case for case in dataset['cases']}, - load_jsonl(source/'results.jsonl'), content_binding) + records, content_binding) sampled = dataset.get("comparison", {}).get("kind") == "webmainbench_frozen_html_to_markdown" if not sampled: reliability = json.loads((source / "reliability.json").read_text()) @@ -55,7 +66,7 @@ def validate_release(source: Path, *, presentation_binding: Path | None = None) raise ValueError("Refusing to package an incomplete or unjudged benchmark report") if dataset.get("comparison", {}).get("require_independent_result_review"): from .result_review import require_result_review - require_result_review(source, dataset, load_jsonl(source / "results.jsonl")) + require_result_review(source, dataset, records) return False review = json.loads((source / "result-review.json").read_text()) if review.get("status") != "passed" or review.get("failures"): @@ -71,7 +82,6 @@ def validate_release(source: Path, *, presentation_binding: Path | None = None) or set(review.get("providers", [])) != set(providers) ): raise ValueError("Report/review scope differs from the declared matrix") - records = load_jsonl(source / "results.jsonl") expected = {(provider, case) for provider in providers for case in cases} if ( len(records) != len(expected) @@ -119,6 +129,8 @@ def _build_content(source, destination, archive, *, webmainbench, raw_blocks, de footprint_projections = [] source_paths = sorted(path for path in source.rglob("*") if path.is_file()) source_names = {path.relative_to(source).as_posix() for path in source_paths} + if "publication-privacy.json" in source_names: + raise ValueError("Publication source contains a reserved generated file: publication-privacy.json") source_file_count = sum(path.name != ".generated-report" for path in source_paths) logical_route_count = sum(path.suffix == ".html" for path in source_paths) @@ -534,7 +546,7 @@ def _archive_identity(path: Path): return hashlib.file_digest(stream, "sha256").hexdigest() -def build(source: Path, archive: Path, *, presentation_binding: Path | None = None, cached_content: Path | None = None, parts_directory: Path | None = None, refresh_privacy=False): +def build(source: Path, archive: Path, *, presentation_binding: Path | None = None, cached_content: Path | None = None, parts_directory: Path | None = None, refresh_privacy=False, snapshot_audit=False): if parts_directory is not None: from .safety import unlinked_path parts_directory = unlinked_path(parts_directory) @@ -551,7 +563,7 @@ def build(source: Path, archive: Path, *, presentation_binding: Path | None = No archive.parent.mkdir(parents=True, exist_ok=True) target = archive.resolve() previous = _archive_identity(target) - sampled = validate_release(source, presentation_binding=presentation_binding) + sampled = validate_release(source, presentation_binding=presentation_binding, snapshot_audit=snapshot_audit) with tempfile.TemporaryDirectory(prefix=".webfetch-package-", dir=archive.parent) as work: staging = Path(work) staged_archive = staging / "package.zip" @@ -564,7 +576,7 @@ def build(source: Path, archive: Path, *, presentation_binding: Path | None = No if parts_directory is None: _write_archive(staging/'content', staged_archive) from .publication_check import check - check(staged_archive, source, presentation_binding=presentation_binding) + check(staged_archive, source, presentation_binding=presentation_binding, snapshot_audit=snapshot_audit) if evidence_tree_identity(cached_content) != cached_identity: raise ValueError('Cached public content changed during packaging') if parts_directory is not None: @@ -572,7 +584,7 @@ def build(source: Path, archive: Path, *, presentation_binding: Path | None = No partition_content(staging/'content',staged_parts) _write_archive(staging/'content',staged_archive) from .publication_check import check - check(staged_archive,source,presentation_binding=presentation_binding, + check(staged_archive,source,presentation_binding=presentation_binding,snapshot_audit=snapshot_audit, external_archives=part_archives(staged_parts/'parts.json'),allow_pending_external=True) manifest = _wire_manifest(staging/'content') if evidence_tree_identity(source) != before: @@ -751,16 +763,19 @@ def main(*, root: Path | None = None): parser.add_argument( "archive", nargs="?", type=Path, default=root / "publish/webfetch-sites.zip" ) + parser.add_argument("--snapshot-audit",action="store_true",help="Package an explicitly marked published-snapshot score audit") parser.add_argument("--incremental", type=Path, help="Write an explanatory update against the baseline archive") parser.add_argument("--presentation-binding", type=Path, help="Validate a frozen diagnostic snapshot without certifying it") parser.add_argument("--cached-content", type=Path, help="Reuse cached public files and refresh source-bound result projections") parser.add_argument("--refresh-privacy", action="store_true", help="Refresh cached public projections with the current privacy policy") parser.add_argument("--parts-directory", type=Path, help="Prepare capacity-bound data attachments before publication") args = parser.parse_args() + if args.incremental and args.snapshot_audit: + parser.error('Snapshot score revisions require a complete package') if args.incremental: print(json.dumps(build_incremental(args.source, args.archive, args.incremental, presentation_binding=args.presentation_binding), ensure_ascii=False)) return - build(args.source, args.archive, presentation_binding=args.presentation_binding, cached_content=args.cached_content, parts_directory=args.parts_directory, refresh_privacy=args.refresh_privacy) + build(args.source, args.archive, presentation_binding=args.presentation_binding, cached_content=args.cached_content, parts_directory=args.parts_directory, refresh_privacy=args.refresh_privacy, snapshot_audit=args.snapshot_audit) print(args.archive.resolve()) diff --git a/harness/web_fetch/report_publish.py b/harness/web_fetch/report_publish.py index f18c61947..d80afa2e3 100644 --- a/harness/web_fetch/report_publish.py +++ b/harness/web_fetch/report_publish.py @@ -47,6 +47,7 @@ class PublishConfig: presentation_binding: Path | None = None visibility: str | None = None parts_manifest: Path | None = None + snapshot_audit: bool = False def response_json(status, raw, *, operation): @@ -107,7 +108,7 @@ def publish(config: PublishConfig, *, transport=None, package_check=None, valida raise ValueError("Unknown publication mode") if config.operation_namespace == 'webfetch': from .report_package import validate_release - validate_release(config.source, presentation_binding=config.presentation_binding) + validate_release(config.source, presentation_binding=config.presentation_binding,snapshot_audit=config.snapshot_audit) if config.visibility not in {None, "private", "unlisted", "public"}: raise ValueError("Unknown publication visibility") if config.presentation_binding is not None: @@ -118,9 +119,9 @@ def publish(config: PublishConfig, *, transport=None, package_check=None, valida attachments = part_archives(config.parts_manifest) parts_plan = json.loads(config.parts_manifest.read_text()) validation_inputs = (*validation_inputs,config.parts_manifest,*attachments.values()) - if package_check is None and (config.presentation_binding is not None or config.parts_manifest is not None): + if package_check is None and (config.presentation_binding is not None or config.parts_manifest is not None or config.snapshot_audit): from functools import partial - package_check = partial(check,presentation_binding=config.presentation_binding,external_archives=attachments) + package_check = partial(check,presentation_binding=config.presentation_binding,external_archives=attachments,snapshot_audit=config.snapshot_audit) protected = {config.profile.resolve(), config.archive.resolve(), *(Path(path).resolve() for path in validation_inputs)} if config.receipt.resolve() in protected or config.journal.resolve() in protected: @@ -337,7 +338,7 @@ def main(*, root: Path | None = None): mode = parser.add_mutually_exclusive_group() mode.add_argument("--create", action="store_true") mode.add_argument("--update", action="store_true") - parser.add_argument("--profile", type=Path, default=root / "evaluation/current-report.json") + parser.add_argument("--profile", type=Path, required=True) parser.add_argument("--receipt", type=Path, default=root / "publish/artifact-hub-receipt.json") parser.add_argument("--archive", type=Path, default=root / "publish/webfetch-sites.zip") parser.add_argument("--source", type=Path, default=root / "reports/webfetch") @@ -353,6 +354,7 @@ def main(*, root: Path | None = None): parser.add_argument("--presentation-binding", type=Path) parser.add_argument("--parts-manifest", type=Path) parser.add_argument("--visibility", choices=("private", "unlisted", "public")) + parser.add_argument("--snapshot-audit",action="store_true",help="Publish a validated saved-output score audit") args = parser.parse_args() result = publish( PublishConfig( @@ -365,7 +367,7 @@ def main(*, root: Path | None = None): args.title, args.base_url, args.expected_version, - presentation_binding=args.presentation_binding, visibility=args.visibility, parts_manifest=args.parts_manifest, + presentation_binding=args.presentation_binding, visibility=args.visibility, parts_manifest=args.parts_manifest,snapshot_audit=args.snapshot_audit, ) ) print(json.dumps(result, ensure_ascii=False), flush=True) diff --git a/harness/web_fetch/rescoring.py b/harness/web_fetch/rescoring.py index 1b300cff3..237ddba4f 100644 --- a/harness/web_fetch/rescoring.py +++ b/harness/web_fetch/rescoring.py @@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[2] from harness.record_io import parse_jsonl -from harness.web_fetch.artifacts import verify_record_artifacts +from harness.web_fetch.artifacts import verify_record_artifacts, file_scoring_metadata from harness.web_fetch.failure_diagnosis import diagnose from lexbench_webfetch.metrics import summarize from harness.web_fetch.models import execution_id @@ -42,7 +42,7 @@ def _rescore_one(arguments): status_code=r.get("status_code"), title=r.get("title"), final_url=r.get("final_url"), - metadata=r.get("provider_metadata", {}), + metadata=file_scoring_metadata(c, r.get("provider_metadata", {}), source), previous_metrics=r["metrics"], list_reference=load_list_reference(c, source), ) @@ -202,3 +202,114 @@ def main(argv=None): if __name__ == "__main__": main() + + +def _score_published_row(arguments): + """Score verified saved bytes without changing a physical call's facts.""" + record, case, path, digest, view = arguments + raw = Path(path).read_bytes() + if hashlib.sha256(raw).hexdigest() != digest: + raise ValueError('Published scoring input changed: ' + record['attempt_id']) + result = deepcopy(record) + result['metrics'] = score_attempt(case, raw, ok=record['ok'], + metadata=record.get('provider_metadata'), previous_metrics=record['metrics'], + status_code=record.get('status_code'), title=record.get('title'), + final_url=record.get('final_url')) + result.pop('diagnosis', None) + result.pop('failure_category', None) + result['scoring'] = {'mode': 'published_snapshot_rescore', + 'source_sha256': SCORING_SOURCE_SHA256, 'provider_calls_added': 0, + 'artifact_view': view, 'original_content_sha256': record['metrics']['content_sha256'], + 'scored_content_sha256': digest} + return result + + +def score_published_snapshot(source, dataset, *, jobs=1, recovered=None, progress=None): + """Recompute a published matrix, disclosing privacy views per result. + + This explicit diagnostic boundary cannot certify a cohort or replace private + evidence. Every changed artifact must match the saved publication receipt; + an exact recovered original takes precedence over its public view. + """ + from concurrent.futures import ProcessPoolExecutor + from collections import deque + from harness.record_io import file_sha256 + from lexbench_webfetch.metrics import task_passed + if type(jobs) is not int or not 1 <= jobs <= 8: + raise ValueError('Offline scoring jobs must be between 1 and 8') + source = Path(source).resolve() + records = parse_jsonl(contained_path(source, 'results.jsonl').read_bytes()) + original = json.loads(contained_path(source, 'dataset.json').read_text()) + validate_report_ids(original, records) + cases = {case['id']: case for case in dataset['cases']} + old_cases = {case['id']: case for case in original['cases']} + from .scoring_revision import ALLOWED_FIELDS + if cases.keys() != old_cases.keys() or dataset.get('coverage_plan') != original.get('coverage_plan'): + raise ValueError('Published rescoring must retain the complete matrix') + for key, prior in old_cases.items(): + changes = {field for field in prior.keys() | cases[key].keys() + if prior.get(field) != cases[key].get(field)} + if changes - ALLOWED_FIELDS: + raise ValueError('Published rescoring changed collection inputs: ' + key) + privacy_path = contained_path(source, 'publication-privacy.json') + privacy = json.loads(privacy_path.read_text()) if privacy_path.is_file() else {'files': []} + views = {item['path']: item for item in privacy['files'] if item.get('scored_output')} + recovered = recovered or {} + unknown = set(recovered) - {row['artifact_path'] for row in records} + if unknown: + raise ValueError('Recovered output is outside the matrix') + requests, inputs = [], [] + for row in records: + relative = row['artifact_path'] + path = contained_path(source, relative, reject_symlinks=True) + published_hash = file_sha256(path) + original_hash = row['metrics']['content_sha256'] + if published_hash != original_hash: + proof = views.get(relative, {}) + if (proof.get('original_sha256') != original_hash + or proof.get('published_sha256') != published_hash + or proof.get('published_bytes') != path.stat().st_size): + raise ValueError('Output lacks an exact privacy binding: ' + row['attempt_id']) + view = 'published_projection' if published_hash != original_hash else 'original' + if relative in recovered: + path = Path(recovered[relative]) + if path.is_symlink() or file_sha256(path) != original_hash: + raise ValueError('Recovered original hash differs: ' + row['attempt_id']) + digest, view = original_hash, 'recovered_original' + else: + digest = published_hash + inputs.append({'attempt_id': row['attempt_id'], 'path': relative, + 'original_sha256': original_hash, 'published_sha256': published_hash, + 'scored_sha256': digest, 'view': view}) + requests.append((row, cases[row['case_id']], str(path), digest, view)) + results = [] + def accept(result): + results.append(result) + if progress: + progress(len(results), len(records)) + if jobs == 1: + for request in requests: + accept(_score_published_row(request)) + else: + with ProcessPoolExecutor(max_workers=jobs) as pool: + remaining = iter(requests) + pending = deque(pool.submit(_score_published_row, request) + for request in [next(remaining, None) for _ in range(jobs)] + if request is not None) + while pending: + accept(pending.popleft().result()) + following = next(remaining, None) + if following is not None: + pending.append(pool.submit(_score_published_row, following)) + from harness.record_io import canonical_json_sha256 + receipt = {'kind': 'published_snapshot_rescore', 'provider_calls_added': 0, + 'corrected_dataset_sha256': canonical_json_sha256(dataset), + 'input_dataset_sha256': file_sha256(source / 'dataset.json'), + 'input_results_sha256': file_sha256(source / 'results.jsonl'), + 'scoring_source_sha256': SCORING_SOURCE_SHA256, 'inputs': inputs, + 'result_count': len(results), 'case_count': len(cases), + 'projected_scoring_inputs': sum(item['view'] == 'published_projection' for item in inputs), + 'recovered_originals': sum(item['view'] == 'recovered_original' for item in inputs), + 'task_passes': sum(task_passed(row['ok'], row['metrics'], cases[row['case_id']].get('task_kind','body')) + for row in results)} + return results, receipt diff --git a/harness/web_fetch/runner.py b/harness/web_fetch/runner.py index a3727bb0c..5bfd300f5 100644 --- a/harness/web_fetch/runner.py +++ b/harness/web_fetch/runner.py @@ -1020,10 +1020,12 @@ def rescore_evaluation( raise ValueError(f"run contains case absent from dataset: {record['case_id']}") artifact = contained_path(run_dir,record["artifact_path"]) previous_deadline = bool(record.get("metrics", {}).get("deadline_overrun")) + from .artifacts import file_scoring_metadata record["metrics"] = score_attempt( case, artifact.read_bytes(), ok=bool(record.get("ok")), title=record.get('title'), final_url=record.get('final_url'), - metadata=record.get("provider_metadata", {}), previous_metrics=record["metrics"], + metadata=file_scoring_metadata(case, record.get("provider_metadata", {}), run_dir), + previous_metrics=record["metrics"], list_reference=load_list_reference(case, dataset_path.parent), status_code=record.get('status_code'), ) diff --git a/harness/web_fetch/scoring_revision.py b/harness/web_fetch/scoring_revision.py index dbb15f43a..adfe0d137 100644 --- a/harness/web_fetch/scoring_revision.py +++ b/harness/web_fetch/scoring_revision.py @@ -35,6 +35,7 @@ "acceptance_contract", "composite_acceptance", "forbidden_any", + "diagnostic_any", "expected_resources", "resource_output_contract", "task_prompt", diff --git a/harness/web_fetch/wcxb_latency.py b/harness/web_fetch/wcxb_latency.py index cbe3e0626..54995dcf8 100644 --- a/harness/web_fetch/wcxb_latency.py +++ b/harness/web_fetch/wcxb_latency.py @@ -254,7 +254,7 @@ def certify(argv=None) -> dict: parser.add_argument('--dataset', type=Path, required=True) parser.add_argument('--sample-manifest', type=Path, required=True) parser.add_argument('--evidence-root', type=Path, required=True) - parser.add_argument('--profile', type=Path, default=Path('evaluation/wcxb/current-report.json')) + parser.add_argument('--profile', type=Path, required=True) args = parser.parse_args(argv) reject_evidence_symlinks(contained_path(args.dataset, 'test', reject_symlinks=True)) certificate_path = args.evidence_root / 'certification.json' @@ -310,7 +310,7 @@ def main(argv=None, *, root: Path | None = None) -> None: parser = argparse.ArgumentParser(description='Collect complete profile-bound WCXB paired timing') for name in ('dataset', 'manifest', 'release', 'candidate', 'output'): parser.add_argument('--' + name, type=Path, required=True) - parser.add_argument('--profile', type=Path, default=(root or Path.cwd()) / 'evaluation/wcxb/current-report.json') + parser.add_argument('--profile', type=Path, required=True) parser.add_argument('--config', type=Path, help='Provider config with release and pr role entries') parser.add_argument('--wait-quiet-seconds', type=int, default=0) args = parser.parse_args(argv) diff --git a/harness/web_fetch/wcxb_publication.py b/harness/web_fetch/wcxb_publication.py index 54d7bcd4f..dc8ec62bf 100644 --- a/harness/web_fetch/wcxb_publication.py +++ b/harness/web_fetch/wcxb_publication.py @@ -57,12 +57,12 @@ def inventory(report: Path, profile: dict) -> dict[str, str]: return {**expected, INVENTORY: actual[INVENTORY]} -def package(source: Path, archive: Path, receipt: Path, *, authority_root: Path) -> dict: +def package(source: Path, archive: Path, receipt: Path, *, authority_root: Path, profile_path: Path | None = None) -> dict: from harness.reporting.links import check as check_links from harness.web_fetch.wcxb_report_check import check as check_report - profile_path = authority_root / 'evaluation/wcxb/current-report.json' - binding_path = authority_root / 'evaluation/wcxb/report-content-binding.json' + profile_path = profile_path or authority_root / 'evaluation/wcxb/current-report.json' + binding_path = profile_path.with_name('report-content-binding.json') reject_evidence_symlinks(profile_path) reject_evidence_symlinks(binding_path) profile = json.loads(profile_path.read_bytes()) @@ -71,7 +71,7 @@ def package(source: Path, archive: Path, receipt: Path, *, authority_root: Path) def validate(): inventory(source, profile) - check_report(source, authority_root=authority_root) + check_report(source, authority_root=authority_root, profile_path=profile_path) check_links(source) def attest(staged): @@ -88,10 +88,10 @@ def attest(staged): validate=validate, attest=attest, excluded=['.generated-report']) -def publish(*, source: Path, authority_root: Path, work: Path, transport=None) -> dict: +def publish(*, source: Path, authority_root: Path, work: Path, transport=None, profile_path: Path | None = None) -> dict: """Resume the same staged update and verify every deployed byte before binding it.""" - profile_path = authority_root / 'evaluation/wcxb/current-report.json' - binding_path = authority_root / 'evaluation/wcxb/report-content-binding.json' + profile_path = profile_path or authority_root / 'evaluation/wcxb/current-report.json' + binding_path = profile_path.with_name('report-content-binding.json') reject_evidence_symlinks(profile_path) profile_bytes = profile_path.read_bytes() profile = json.loads(profile_bytes) @@ -102,7 +102,7 @@ def publish(*, source: Path, authority_root: Path, work: Path, transport=None) - raise ValueError('WCXB publication requires a bound site') share_path, _ = public_share_context(base, profile.get('artifact_hub_share_url', '')) archive, proof = work / 'wcxb-current.zip', work / 'wcxb-package.json' - attestation = package(source, archive, proof, authority_root=authority_root) + attestation = package(source, archive, proof, authority_root=authority_root, profile_path=profile_path) identity = {'base_url': base, 'slug': slug, 'archive_sha256': attestation['archive_sha256']} # The release identifies the operation; its mutable live version is a guard. @@ -200,11 +200,20 @@ def main(argv=None, *, root: Path | None = None) -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--authority-root', type=Path, default=root or Path.cwd()) parser.add_argument('--source', type=Path) + parser.add_argument('--profile', type=Path, required=True, help='external WCXB instance profile') + parser.add_argument('--package-only', action='store_true', help='validate and create a local archive without publishing') parser.add_argument('--work', type=Path, default=data_home() / 'publication') args = parser.parse_args(argv) authority = args.authority_root.absolute() + if args.package_only: + archive, receipt = args.work / 'wcxb-current.zip', args.work / 'wcxb-package.json' + proof = package(args.source or authority / 'reports/wcxb', archive, receipt, + authority_root=authority, profile_path=args.profile) + print(json.dumps({'archive': str(archive), 'archive_sha256': proof['archive_sha256'], + 'files': len(proof['files'])})) + return print(json.dumps(publish(source=args.source or authority / 'reports/wcxb', - authority_root=authority, work=args.work), ensure_ascii=False)) + authority_root=authority, work=args.work, profile_path=args.profile), ensure_ascii=False)) if __name__ == '__main__': diff --git a/harness/web_fetch/wcxb_report_check.py b/harness/web_fetch/wcxb_report_check.py index 0b02d347c..501e8b83a 100644 --- a/harness/web_fetch/wcxb_report_check.py +++ b/harness/web_fetch/wcxb_report_check.py @@ -10,9 +10,11 @@ from collections import Counter from html.parser import HTMLParser from pathlib import Path +from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit from bs4 import BeautifulSoup +from harness.web_fetch.safety import reject_evidence_symlinks from harness.record_io import load_jsonl from harness.report_languages import localized_report_routes, visible_numbers from harness.reporting.presentation import check_presentation @@ -80,11 +82,16 @@ def __init__(self): def handle_starttag(self, tag, attrs): values = dict(attrs) - self.tags[tag] += 1 + if not (tag == 'script' and 'data-report-language-entry' in values): + self.tags[tag] += 1 if values.get('id'): self.ids.add(values['id']) if tag == 'a' and values.get('href'): - self.links[re.sub(r'^en/', '', values['href'])] += 1 + href = urlsplit(values['href']) + query = [(key, value) for key, value in parse_qsl(href.query, keep_blank_values=True) + if href.netloc or href.scheme or key != 'report-language'] + path = re.sub(r'^en/', '', href.path) if not href.netloc else href.path + self.links[urlunsplit((href.scheme, href.netloc, path, urlencode(query), href.fragment))] += 1 for name in ('value', 'data-provider', 'data-case', 'data-result', 'data-diagnosis', 'data-page-type', 'data-sort', 'data-core-sort'): if name in values: @@ -139,37 +146,99 @@ def require_analysis_controls(html: str | BeautifulSoup, result_count: int) -> B def require_home_interpretation(html: str | BeautifulSoup, summary: dict, - paired_latency: dict, lang: str, paired_methods: dict) -> BeautifulSoup: - """Keep the version comparison readable and its timing caveat beside the table.""" + paired_latency: dict | None, lang: str, + paired_methods: dict | None = None, *, + scores: list[dict] | None = None, + comparison: dict | None = None) -> BeautifulSoup: + """Check declared page-level changes and independently bound paired timing.""" soup = BeautifulSoup(html, 'html.parser') if isinstance(html, str) else html core = soup.select_one('#wcxb-core') contexts = soup.select('#paired-latency-context') + changes = soup.select('#configuration-change') + require(core is not None, 'Home core comparison is missing') require(not soup.select('#moli-change'), 'Report-specific findings belong in bound interpretation content') - require(core is not None and len(contexts) == 1, - 'Home paired comparison or timing context is missing or duplicated') - context = contexts[0] - require(core.find_parent('section') == context.find_parent('section'), - 'Paired timing context must be beside the core comparison') - require(core.find_next(id='paired-latency-context') == context, - 'Paired timing context must follow the core comparison') - pair = {role: provider for provider, role in paired_methods.items()} - release = summary['providers'][pair['release']]['mean_latency_ms'] - candidate = summary['providers'][pair['pr']]['mean_latency_ms'] - required = (f'{release:,.0f} ms', f'{candidate:,.0f} ms', - f'{paired_latency["paired_delta_mean_ms"]:+.1f} ms') - context_text = context.get_text(' ', strip=True) - require(all(value in context_text for value in required), - 'Home timing context disagrees with measured evidence') - require(('交叉顺序' if lang == 'zh' else 'balanced same-window') in context_text - and re.search(r'(? 1e-10 for value in deltas), + 'lower': sum(value < -1e-10 for value in deltas), + 'equal': sum(abs(value) <= 1e-10 for value in deltas)} + actual = {node['data-f1-relation']: int(node['data-count']) + for node in change.select('[data-f1-relation][data-count]')} + require(actual == expected and sum(actual.values()) == summary['quality_case_count'], + 'Home page-level change counts differ from scored evidence') + for node in change.select('a[href*="cases/"]'): + require(Path(urlsplit(node['href']).path).stem in by_case, + 'Home comparison links to a case outside the selected evidence') + require(('逐页 F1 的平均' if lang == 'zh' else 'page-level F1 values') in change_text, + 'Home comparison omits the page-level F1 definition') + precision_delta = ((summary['providers'][candidate]['quality_precision'] + - summary['providers'][baseline]['quality_precision']) * 100) + recall_delta = ((summary['providers'][candidate]['quality_recall'] + - summary['providers'][baseline]['quality_recall']) * 100) + f1_delta = ((summary['providers'][candidate]['quality_f1'] + - summary['providers'][baseline]['quality_f1']) * 100) + require(all(f'{value:+.4f}' in change_text + for value in (precision_delta, recall_delta, f1_delta)), + 'Home configuration change summary disagrees with aggregate deltas') + require(not any(f'{summary["providers"][provider]["quality_f1"] * 100:.4f}%' + in change_text for provider in (baseline,candidate)), + 'Home configuration change summary repeats exact table values') return soup def require_guide_interpretation(html: str | BeautifulSoup, summary: dict, providers: list[str], lang: str, *, expected_html: str) -> BeautifulSoup: + """A guide must work as a standalone explanation, with traceable examples.""" soup = BeautifulSoup(html, 'html.parser') if isinstance(html, str) else html expected = BeautifulSoup(expected_html, 'html.parser') @@ -200,12 +269,17 @@ def require_guide_interpretation(html: str | BeautifulSoup, summary: dict, for graphic in soup.select('main img, .repair-case img'): require(bool(graphic.get('src')) and bool(graphic.get('alt', '').strip()), 'Guide figure lacks a source or alternative text') + require(soup.select_one('#guide-methods table') is not None and len(soup.select('#guide-methods tbody tr')) == len(providers), 'Guide does not diagnose every measured configuration') - require(soup.select_one('#guide-timing a[href="research/wcxb-full-paired-latency/calls.jsonl"]') is not None - and soup.select_one('#guide-timing a[href="research/wcxb-full-paired-latency/certification.json"]') is not None, - 'Guide timing is missing full-scope certified paired evidence') + specimens = [specimen] + for sibling in specimen.find_next_siblings(): + if sibling.get('id') in anchors: + break + specimens.append(sibling) + require(any(node.select_one('a[href*="cases/"]') is not None for node in specimens), + 'Guide lacks inspectable source/reference/output evidence') require(len(soup.select_one('#guide-conclusion').get_text(' ', strip=True)) > 200, 'Guide conclusion lacks a substantive finding') return soup @@ -232,22 +306,28 @@ def check_bound_interpretation(root: Path, binding: dict) -> None: raise ValueError('WCXB interpretation differs from bound content: ' + language) -def check(root: Path, *, authority_root: Path | None = None) -> None: +def check(root: Path, *, authority_root: Path | None = None, profile_path: Path | None = None) -> None: root = root.resolve() repository = (authority_root or ROOT).resolve() - profile_path = repository / "evaluation/wcxb/current-report.json" + if profile_path is None: + if authority_root is None: + raise ValueError("Select an external report instance with --profile or --authority-root") + profile_path = repository / "evaluation/wcxb/current-report.json" if not profile_path.is_file(): - raise ValueError("WCXB release authority is missing; pass --authority-root pointing to the repository") - profile = json.loads((repository / 'evaluation/wcxb/current-report.json').read_text()) + raise ValueError("WCXB release authority is missing; pass --authority-root pointing to the selected instance bundle") + reject_evidence_symlinks(profile_path) + profile = json.loads(profile_path.read_text()) + comparison = profile.get('paired_comparison') or {} from .wcxb_publication import inventory inventory(root, profile) - binding_path = contained_path(repository, 'evaluation/wcxb/report-content-binding.json', - reject_symlinks=True) + binding_path = profile_path.with_name('report-content-binding.json') + reject_evidence_symlinks(binding_path) content_binding = json.loads(binding_path.read_bytes()) check_bound_interpretation(root, content_binding) guide_sources = {lang: (root / prefix / 'guide/index.html').read_text() for lang, prefix in [('zh', ''), ('en', 'en')] if (root / prefix / 'guide/index.html').is_file()} + sample_file = root / 'sample-manifest.json' sample = json.loads(sample_file.read_text()) require(sample.get('scope') == 'qualified_test', 'WCXB current selection is not source-qualified') @@ -267,50 +347,56 @@ def check(root: Path, *, authority_root: Path | None = None) -> None: require(len(case_ids) == profile['case_count'] == summary['case_count'], 'WCXB case count differs') require(len(scores) == len(case_ids) * len(providers), 'WCXB score count differs') require(summary['quality_case_count'] == profile['primary_quality_case_count'] == sample['primary_quality_case_count'] == len(qualified), 'WCXB quality denominator differs') + require(summary['exclusions'] == sample.get('exclusions', []) == [], 'WCXB current cohort contains excluded cases') require(summary['benchmark'] == sample['dataset_id'] == certificate['benchmark'], 'WCXB certified dataset identity differs') require({(r['provider'], r['case_id']) for r in scores} == {(p, c) for p in providers for c in case_ids}, 'WCXB method-case matrix differs') require([r['provider'] for r in certificate['providers']] == providers, 'WCXB certificate participants differ') from harness.web_fetch.wcxb_scoring import paired_latency_methods - paired_methods = paired_latency_methods(profile, certificate) - optional_paths = [name for name, sha in content_binding['evidence'].items() - if sha == profile['optional_ui_receipt_sha256']] - require(len(optional_paths) == 1, 'Optional feature receipt must have one profile binding') - optional_source = Path(optional_paths[0]) - optional_public = Path('research') / ('wcxb-' + optional_source.parent.name) / optional_source.name - optional_receipt = contained_path(root, optional_public, reject_symlinks=True) - require(optional_receipt.is_file() and digest(optional_receipt) == profile['optional_ui_receipt_sha256'], - 'WCXB optional feature evidence differs from current profile') - optional_root = optional_receipt.parent - optional = json.loads(optional_receipt.read_text()) - require(optional.get('case_id') in case_ids, 'Optional feature case is outside the current population') - for name, binary_hash in (('release', profile['release_binary_sha256']), - ('candidate', profile['candidate_binary_sha256'])): - build = optional['builds'][name] - artifact = optional_root / f'{name}.md' - require(build['binary_sha256'] == binary_hash and build['returncode'] == 0 - and artifact.is_file() and not artifact.is_symlink() - and digest(artifact) == build['output_sha256'], - 'WCXB optional UI-stripping output is not bound to its binary') - require(all(isinstance(build.get('description_present'), bool) - for build in optional['builds'].values()), - 'WCXB optional feature evidence lacks observed outcomes') + paired_methods = paired_latency_methods(profile, certificate) if profile.get('latency_correction_receipt_sha256') else {} + if profile.get('optional_ui_receipt_sha256'): + optional_paths = [name for name, sha in content_binding['evidence'].items() + if sha == profile['optional_ui_receipt_sha256']] + require(len(optional_paths) == 1, 'Optional feature receipt must have one profile binding') + optional_source = Path(optional_paths[0]) + optional_public = Path('research') / ('wcxb-' + optional_source.parent.name) / optional_source.name + optional_receipt = contained_path(root, optional_public, reject_symlinks=True) + require(optional_receipt.is_file() and digest(optional_receipt) == profile['optional_ui_receipt_sha256'], + 'WCXB optional feature evidence differs from current profile') + optional_root = optional_receipt.parent + optional = json.loads(optional_receipt.read_text()) + require(optional.get('case_id') in case_ids, 'Optional feature case is outside the current population') + for name, binary_hash in (('release', profile['release_binary_sha256']), + ('candidate', profile['candidate_binary_sha256'])): + build = optional['builds'][name] + artifact = optional_root / f'{name}.md' + require(build['binary_sha256'] == binary_hash and build['returncode'] == 0 + and artifact.is_file() and not artifact.is_symlink() + and digest(artifact) == build['output_sha256'], + 'WCXB optional UI-stripping output is not bound to its binary') + require(all(isinstance(build.get('description_present'), bool) + for build in optional['builds'].values()), + 'WCXB optional feature evidence lacks observed outcomes') + from harness.web_fetch.wcxb_latency import ( load_full_paired_latency, summarize_full_paired_latency, ) - _, selected_latency = load_full_paired_latency(root / 'research/wcxb-full-paired-latency', profile, case_ids) - paired_latency = summarize_full_paired_latency(selected_latency, case_ids) - for provider, method in paired_methods.items(): - values = sorted(selected_latency[case_id, method]['elapsed_ms'] for case_id in case_ids) - position = (len(values) - 1) * .95 - low = int(position) - p95 = values[low] + (values[min(low + 1, len(values) - 1)] - values[low]) * (position - low) - measured = summary['providers'][provider] - require(abs(measured['mean_latency_ms'] - paired_latency[method]['mean_ms']) < .01 - and abs(measured['p50_latency_ms'] - paired_latency[method]['p50_ms']) < .01 - and abs(measured['p95_latency_ms'] - p95) < .01, - 'WCXB Paired timing summary differs from certified full paired evidence') + paired_latency = None + if profile.get('latency_correction_receipt_sha256'): + _, selected_latency = load_full_paired_latency(root / 'research/wcxb-full-paired-latency', profile, case_ids) + paired_latency = summarize_full_paired_latency(selected_latency, case_ids) + for provider, method in paired_methods.items(): + values = sorted(selected_latency[case_id, method]['elapsed_ms'] for case_id in case_ids) + position = (len(values) - 1) * .95 + low = int(position) + p95 = values[low] + (values[min(low + 1, len(values) - 1)] - values[low]) * (position - low) + measured = summary['providers'][provider] + require(abs(measured['mean_latency_ms'] - paired_latency[method]['mean_ms']) < .01 + and abs(measured['p50_latency_ms'] - paired_latency[method]['p50_ms']) < .01 + and abs(measured['p95_latency_ms'] - p95) < .01, + 'WCXB Paired timing summary differs from certified full paired evidence') + evidence_hashes = json.loads((root / 'evidence-manifest.json').read_text()) require(set(evidence_hashes) == case_ids, 'WCXB evidence inventory differs') by_case = {} @@ -367,7 +453,8 @@ def check(root: Path, *, authority_root: Path | None = None) -> None: if route in ('index.html', 'datasets/index.html'): check_presentation(html) if route == 'index.html': - require_home_interpretation(html, summary, paired_latency, lang, paired_methods) + require_home_interpretation(html, summary, paired_latency, lang, paired_methods, scores=scores, comparison=comparison) + require('id="home-type"' in html, 'Page-type filter missing') require('官方全量 F1' not in html and 'Official full F1' not in html, 'Invalid full-reference score remains visible') first_card = BeautifulSoup(html, 'html.parser').select_one('.lead-grid .panel') @@ -421,6 +508,7 @@ def check(root: Path, *, authority_root: Path | None = None) -> None: else: raise AssertionError('A broken bilingual navigation link was accepted') require_comparison_highlights((root / 'index.html').read_text()) + analysis = require_analysis_controls((root / 'analysis/index.html').read_text(), len(qualified) * len(providers)) analysis.select_one('#diagnosis-dialog [data-diagnosis-link]').decompose() try: @@ -444,7 +532,7 @@ def check(root: Path, *, authority_root: Path | None = None) -> None: # filenames, versions or claims. Any edited authored evidence must fail. for lang, prefix in language_pairs: source = guide_sources[lang] - for selector in ('#guide-results tbody tr', '#guide-cases a', + for selector in ('#guide-results tbody tr', 'a[href*="cases/"]', '#guide-conclusion'): guide = BeautifulSoup(source, 'html.parser') target = guide.select_one(selector) @@ -457,39 +545,41 @@ def check(root: Path, *, authority_root: Path | None = None) -> None: pass else: raise AssertionError('An altered bound interpretation was accepted: ' + selector) - home = BeautifulSoup((root / 'index.html').read_text(), 'html.parser') - home.select_one('#paired-latency-context').decompose() - try: - require_home_interpretation(home, summary, paired_latency, 'zh', paired_methods) - except ValueError: - pass - else: - raise AssertionError('A home page without adjacent timing context was accepted') - with tempfile.TemporaryDirectory(prefix='wcxb-latency-negative-') as directory: - invalid = Path(directory) - evidence = root / 'research/wcxb-full-paired-latency' - shutil.copy2(evidence / 'receipt.json', invalid / 'receipt.json') - shutil.copy2(evidence / 'calls.jsonl', invalid / 'calls.jsonl') - calls = (invalid / 'calls.jsonl').read_bytes() - (invalid / 'calls.jsonl').write_bytes(calls.rsplit(b'\n', 2)[0] + b'\n') + if profile.get('latency_correction_receipt_sha256'): + home = BeautifulSoup((root / 'index.html').read_text(), 'html.parser') + home.select_one('#paired-latency-context').decompose() try: - load_full_paired_latency(invalid, profile, case_ids) + require_home_interpretation(home, summary, paired_latency, 'zh', paired_methods) except ValueError: pass else: - raise AssertionError('An incomplete paired latency cohort was accepted') - (invalid / 'calls.jsonl').write_bytes(calls) - receipt = json.loads((invalid / 'receipt.json').read_text()) - receipt['binary_sha256']['pr'] = '0' * 64 - (invalid / 'receipt.json').write_text(json.dumps(receipt)) - try: - load_full_paired_latency(invalid, profile, case_ids) - except ValueError: - pass - else: - raise AssertionError('A wrong paired binary identity was accepted') - require((root / 'assets/style.css').is_file() and (root / 'assets/report.js').is_file(), 'WCXB report assets missing') - require(len([path for path in root.rglob('*') if path.is_file()]) < 2000, 'WCXB report tree unexpectedly large') + raise AssertionError('A home page without adjacent timing context was accepted') + with tempfile.TemporaryDirectory(prefix='wcxb-latency-negative-') as directory: + invalid = Path(directory) + evidence = root / 'research/wcxb-full-paired-latency' + shutil.copy2(evidence / 'receipt.json', invalid / 'receipt.json') + shutil.copy2(evidence / 'calls.jsonl', invalid / 'calls.jsonl') + calls = (invalid / 'calls.jsonl').read_bytes() + (invalid / 'calls.jsonl').write_bytes(calls.rsplit(b'\n', 2)[0] + b'\n') + try: + load_full_paired_latency(invalid, profile, case_ids) + except ValueError: + pass + else: + raise AssertionError('An incomplete paired latency cohort was accepted') + (invalid / 'calls.jsonl').write_bytes(calls) + receipt = json.loads((invalid / 'receipt.json').read_text()) + receipt['binary_sha256']['pr'] = '0' * 64 + (invalid / 'receipt.json').write_text(json.dumps(receipt)) + try: + load_full_paired_latency(invalid, profile, case_ids) + except ValueError: + pass + else: + raise AssertionError('A wrong paired binary identity was accepted') + + require((root / 'assets/style.css').is_file() and (root / 'assets/report.js').is_file(), 'WCXB report assets missing') + print(f'PASS: WCXB {len(case_ids)}-case, {len(providers)}-method, declared-language evidence and route inventory') @@ -497,8 +587,9 @@ def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('report', type=Path) parser.add_argument('--authority-root', type=Path) + parser.add_argument('--profile', type=Path) args = parser.parse_args() - check(args.report, authority_root=args.authority_root) + check(args.report, authority_root=args.authority_root, profile_path=args.profile) def binary_from_env(path: Path) -> str: @@ -547,7 +638,8 @@ def check_visual(root: Path, browser_binary: str) -> None: errors = [] page.on('pageerror', lambda error: errors.append(str(error))) prefix = 'en/' if lang == 'en' else '' - page.goto(f'{origin}/{prefix}{route}', + language_query = '?report-language=zh' if lang == 'zh' and route == 'index.html' else '' + page.goto(f'{origin}/{prefix}{route}{language_query}', wait_until='networkidle') if not page.locator('link[rel="stylesheet"]').evaluate('(node) => Boolean(node.sheet)'): raise ValueError(f'Report stylesheet did not load: {lang} {route}') @@ -628,7 +720,7 @@ def check_visual(root: Path, browser_binary: str) -> None: if f'cases/{case_id}.html' not in page.url: raise ValueError('Nested result link does not open the case') page.locator('header a').first.click() - if not page.url.endswith('/index.html'): + if urlsplit(page.url).path != urlsplit(f'{origin}/{prefix}index.html').path: raise ValueError('Nested result cannot return to the report root') if route.startswith('cases/') and not dark: page.locator('.hero .source a').click() diff --git a/harness/web_fetch/wcxb_scoring.py b/harness/web_fetch/wcxb_scoring.py index 1958c071b..8610f17c6 100644 --- a/harness/web_fetch/wcxb_scoring.py +++ b/harness/web_fetch/wcxb_scoring.py @@ -603,7 +603,7 @@ def main(*, root: Path | None = None) -> None: parser.add_argument("--sample-manifest", type=Path, required=True) parser.add_argument("--evidence-root", type=Path, required=True) parser.add_argument( - "--profile", type=Path, default=(root or Path.cwd()) / "evaluation/wcxb/current-report.json" + "--profile", type=Path, required=True ) args = parser.parse_args() score( diff --git a/harness/web_fetch/webmainbench_assembly.py b/harness/web_fetch/webmainbench_assembly.py index c0b1a4176..61effa874 100644 --- a/harness/web_fetch/webmainbench_assembly.py +++ b/harness/web_fetch/webmainbench_assembly.py @@ -647,7 +647,7 @@ def sample_main(argv=None, *, root=None): parser.add_argument("--dataset", type=Path, required=True) parser.add_argument("--sample-manifest", type=Path, required=True) parser.add_argument("--review", type=Path, required=True) - parser.add_argument("--profile", type=Path, default=(root or Path.cwd()) / "evaluation/current-report.json") + parser.add_argument("--profile", type=Path, required=True) args = parser.parse_args(argv) assemble(args.source_root, args.output, args.dataset, args.profile, sample_manifest=args.sample_manifest, review_path=args.review) @@ -661,7 +661,7 @@ def main() -> None: parser.add_argument( "--profile", type=Path, - default=ROOT / "evaluation/webmainbench-full/current-report.json", + required=True, ) parser.add_argument( "--variants", type=Path, help="Variant registry bound by the report profile" diff --git a/harness/web_fetch/webmainbench_certification.py b/harness/web_fetch/webmainbench_certification.py index 7ba6e00b7..fa138a823 100644 --- a/harness/web_fetch/webmainbench_certification.py +++ b/harness/web_fetch/webmainbench_certification.py @@ -139,7 +139,7 @@ def main(argv: list[str] | None = None) -> None: parser.add_argument("--output-root", type=Path, required=True) parser.add_argument("--source", type=Path, required=True) parser.add_argument("--scorer", type=Path, required=True) - parser.add_argument("--profile", type=Path, default=Path("evaluation/webmainbench-full/current-report.json")) + parser.add_argument("--profile", type=Path, required=True) parser.add_argument("--config", type=Path, default=Path("configs/providers.headless.json")) parser.add_argument("--env-file", type=Path, help="Explicit local evaluation credentials file") parser.add_argument("--binary-source", type=Path, @@ -241,7 +241,7 @@ def main_score(argv: list[str] | None = None) -> None: parser.add_argument("--cohort", type=Path, required=True) parser.add_argument("--source", type=Path, required=True) parser.add_argument("--scorer", type=Path, required=True) - parser.add_argument("--profile", type=Path, default=Path("evaluation/webmainbench-full/current-report.json")) + parser.add_argument("--profile", type=Path, required=True) args = parser.parse_args(argv) for name in ("cohort", "source", "scorer", "profile"): setattr(args, name, getattr(args, name).absolute()) diff --git a/harness/web_fetch/webmainbench_diagnostics.py b/harness/web_fetch/webmainbench_diagnostics.py index e84ffd345..995f669e1 100644 --- a/harness/web_fetch/webmainbench_diagnostics.py +++ b/harness/web_fetch/webmainbench_diagnostics.py @@ -821,7 +821,7 @@ def sample_review_main(argv=None, *, root=None) -> None: default=None, help="Comma-separated provider shards; the default is the publishable matrix", ) - parser.add_argument("--profile", type=Path, default=(root or Path.cwd()) / "evaluation/current-report.json") + parser.add_argument("--profile", type=Path, required=True) args = parser.parse_args(argv) if args.output.exists() or args.output.is_symlink(): raise ValueError("Review output exists; refusing to overwrite") diff --git a/harness/web_fetch/webmainbench_latency_diagnostic.py b/harness/web_fetch/webmainbench_latency_diagnostic.py index e389d4b82..663f82e8d 100644 --- a/harness/web_fetch/webmainbench_latency_diagnostic.py +++ b/harness/web_fetch/webmainbench_latency_diagnostic.py @@ -134,9 +134,7 @@ def load_diagnostic(source: Path, dataset: dict, official_records: list[dict], m return None manifest_bytes = contained_path(root, 'manifest.json').read_bytes() if expected_manifest_sha256 is None: - profile_path = Path(__file__).resolve().parents[2] / 'evaluation/current-report.json' - profile = json.loads(profile_path.read_text(encoding='utf-8')) - expected_manifest_sha256 = profile.get('latency_diagnostic', {}).get('manifest_sha256') + raise ValueError('Latency diagnostics require an explicit expected manifest hash') if hashlib.sha256(manifest_bytes).hexdigest() != expected_manifest_sha256: raise ValueError('Latency diagnostic manifest differs from the current report profile') manifest = json.loads(manifest_bytes) diff --git a/harness/web_fetch/webmainbench_report_check.py b/harness/web_fetch/webmainbench_report_check.py index d29b3a6c5..e75294726 100644 --- a/harness/web_fetch/webmainbench_report_check.py +++ b/harness/web_fetch/webmainbench_report_check.py @@ -22,6 +22,7 @@ from harness.web_fetch.report import ERROR_PAGE, EXTRA, JS, STYLE from harness.web_fetch.report_changes import webmainbench_history from lexbench_webfetch.paths import contained_path +from harness.web_fetch.safety import reject_evidence_symlinks from harness.web_fetch.webmainbench_diagnostics import validate_teds_revision_if_present from harness.web_fetch.webmainbench_evidence import retry_chain_matches from harness.web_fetch.webmainbench_latency_correction import ( @@ -32,11 +33,12 @@ ROOT = Path(__file__).resolve().parents[2] -def validate_sample_content(root: Path, *, authority_root: Path) -> None: +def validate_sample_content(root: Path, *, authority_root: Path, binding_path: Path | None = None) -> None: from harness.reporting.assets import load_bound_content - expected = contained_path(authority_root, 'evaluation/webmainbench-content-binding.json', - reject_symlinks=True).read_bytes() + selected = binding_path or contained_path(authority_root, 'evaluation/webmainbench-content-binding.json', reject_symlinks=True) + reject_evidence_symlinks(selected) + expected = selected.read_bytes() actual = contained_path(root, 'report-content-binding.json', reject_symlinks=True).read_bytes() if actual != expected: @@ -52,10 +54,12 @@ def validate_sample_content(root: Path, *, authority_root: Path) -> None: def authority_directory(authority_root: Path | None, profile: str) -> Path: - repository = (authority_root or ROOT).resolve() + if authority_root is None: + raise ValueError("Select an external authority bundle with --authority-root") + repository = authority_root.resolve() if not (repository / profile).is_file(): raise ValueError("WebMainBench release authority is missing; pass --authority-root " - "pointing to the repository") + "pointing to the selected instance bundle") return repository @@ -126,6 +130,56 @@ def check_table_interaction(root: Path, *, provider_count: int, case_count: int) raise ValueError('Result inventory lacks complete filters or stable sorting semantics') +def check_latency_diagnostic(section, evidence: dict, *, formal_round=None) -> None: + """Check displayed timing against the selected diagnostic, independent of version names.""" + if section is None: + raise ValueError('Latency diagnostic section is missing') + runs = evidence['balanced_200']['runs'] + table = section.select_one('.wmb-rerun-table') + rows = table.select('tbody tr') if table else [] + if len(rows) != len(runs): + raise ValueError('Latency diagnostic version population differs') + for rendered, (version, run) in zip(rows, runs.items()): + cells = rendered.find_all('td', recursive=False) + if len(cells) != 6 or not cells[1].get_text(strip=True).endswith(version) or cells[2].get_text(strip=True) != f"{run['n']}/{run['calls']}": + raise ValueError('Latency diagnostic version or call count differs') + for cell, metric in zip(cells[3:], ('mean', 'p50', 'p95')): + values = [item[metric] for item in runs.values()] + won = run[metric] == min(values) and values.count(run[metric]) == 1 + if (not close(float(cell['data-sort']), run[metric]) + or len(cell.select('.comparison-winner')) != int(won)): + raise ValueError('Latency diagnostic value or unique winner differs') + context = section.select_one('[data-timing-context]') + if formal_round is not None: + if context is None or context.get('data-formal-round') != str(formal_round) or not context.get_text(strip=True): + raise ValueError('Latency diagnostic must distinguish the separate formal timing') + elif context is not None: + raise ValueError('Latency diagnostic claims an unbound formal timing') + cases = evidence['tail_repeat']['cases'] + case_rows = section.select('.wmb-rerun-cases tbody tr') + if len(case_rows) != len(cases): + raise ValueError('Latency diagnostic repeat population differs') + for rendered, case in zip(case_rows, cases): + cells = rendered.find_all('td', recursive=False) + if len(cells) != 6 or not cells[1].select_one(f'a[href$="datasets/{case["case_id"]}.html"]'): + raise ValueError('Latency diagnostic page evidence differs') + for cell, metric in zip(cells[2:], ('older_median_ms', 'newer_median_ms', 'older_max_ms', 'newer_max_ms')): + if not close(float(cell['data-sort']), case[metric]): + raise ValueError('Latency diagnostic page timing differs') + outliers = [case for case in cases if abs(case['older_median_ms'] - case['newer_median_ms']) > 20] + direction = section.select_one('[data-tail-outliers]') + expected = {'data-tail-outliers': len(outliers), + 'data-tail-newer-faster': sum(case['newer_median_ms'] < case['older_median_ms'] for case in outliers), + 'data-tail-older-faster': sum(case['older_median_ms'] < case['newer_median_ms'] for case in outliers)} + if direction is None or any(direction.get(key) != str(value) for key, value in expected.items()) or not direction.get_text(strip=True): + raise ValueError('Latency diagnostic tail conclusion differs') + if len(section.select('.wmb-rerun-resource tbody tr')) != len(evidence['resource']) or not section.select_one('a[href$="latency-diagnostic.json"]'): + raise ValueError('Latency diagnostic resource population or receipt is missing') + failed_case = (evidence.get('failure_retest') or {}).get('case_id') + if failed_case and not section.select_one(f'a[href$="datasets/{failed_case}.html"]'): + raise ValueError('Latency diagnostic failure specimen is missing') + + def check(root: Path, *, authority_root: Path | None = None) -> None: repo = authority_directory(authority_root, "evaluation/webmainbench-full/current-report.json") profile_path = repo / 'evaluation/webmainbench-full/current-report.json' @@ -464,15 +518,17 @@ def check_teds_warnings(root: Path, impossible_teds: int) -> None: def check_sample_report(root: Path, dataset: dict | None = None, records: list[dict] | None = None, *, - authority_root: Path | None = None) -> None: - repo = authority_directory(authority_root, "evaluation/current-report.json") + authority_root: Path | None = None, profile_path: Path | None = None) -> None: + repo = (profile_path.parent.parent if profile_path is not None else + authority_directory(authority_root, "evaluation/current-report.json")) + profile_path = profile_path or repo / "evaluation/current-report.json" check_language_contract(root) language_pairs = (('', 'zh'), ('en/', 'en')) if 'en' in generated_languages(root) else (('', 'zh'),) prefixes = tuple(prefix for prefix, _ in language_pairs) dataset = dataset or json.loads((root / "dataset.json").read_text(encoding="utf-8")) records = records or read_jsonl(root / "results.jsonl") latency_correction = validate_latency_correction(root) - current_profile = json.loads((repo / 'evaluation/current-report.json').read_text()) + current_profile = json.loads(profile_path.read_text()) diagnostic_profile = current_profile.get('latency_diagnostic') diagnostic_path = root / 'latency-diagnostic.json' if diagnostic_profile and not diagnostic_path.is_file(): @@ -704,63 +760,8 @@ def check_sample_report(root: Path, dataset: dict | None = None, f'a[href$="datasets/{latency_diagnostic["failure_retest"]["case_id"]}.html"]')): raise ValueError(prefix + 'Core coverage lacks the verified fixture-failure explanation') for section in (localized_home.select_one('#moli-latency-rerun'), guide_page.select_one('#delivery .wmb-rerun-diagnostic')): - if section is None or len(section.select('.wmb-rerun-table tbody tr')) != 2: - raise ValueError(prefix + 'Moli latency rerun must show the current two-version comparison') - context = section.select_one('[data-timing-context="prior-overlap"]') - context_markers = (('较早', '同时运行', '正式', '无并发', '第二轮') if not prefix - else ('earlier', 'overlapped', 'formal', 'without concurrent', 'second round')) - if latency_correction and (context is None - or context.get('data-formal-round') != str(latency_correction['selected_round']) - or not all(marker in context.get_text(' ', strip=True) for marker in context_markers)): - raise ValueError(prefix + 'Corrected timing must distinguish the earlier diagnostic run') - if not latency_correction and context is not None: - raise ValueError(prefix + 'Uncorrected timing must not claim a later formal run') - rerun_rows = dict(zip(('1.1.5', '1.1.7'), - section.select('.wmb-rerun-table tbody tr'))) - for name, row in rerun_rows.items(): - cells = row.find_all('td', recursive=False) - run = latency_diagnostic['balanced_200']['runs'][name] - if (cells[-4].get_text(strip=True) != f"{run['n']}/{run['calls']}" - or any(not close(float(td['data-sort']), run[key]) - for td, key in zip(cells[-3:], ('mean', 'p50', 'p95')))): - raise ValueError(prefix + 'Moli latency rerun values differ from verified evidence') - for key, td in zip(('mean', 'p50', 'p95'), cells[-3:]): - other = '1.1.7' if name == '1.1.5' else '1.1.5' - expected = run[key] < latency_diagnostic['balanced_200']['runs'][other][key] - if bool(td.select('.comparison-winner')) != expected or len(td.select('.comparison-winner')) > 1: - raise ValueError(prefix + 'Moli rerun winner marker disagrees with same-host evidence') - case_rows = section.select('.wmb-rerun-cases tbody tr') - if len(case_rows) != 20: - raise ValueError(prefix + 'Moli slow-page inventory must include all 20 cases') - outliers = [row for row in latency_diagnostic['tail_repeat']['cases'] - if abs(row['older_median_ms'] - row['newer_median_ms']) > 20] - direction = section.select_one('[data-tail-outliers]') - if (direction is None - or direction.get('data-tail-outliers') != str(len(outliers)) - or direction.get('data-tail-newer-faster') != str(sum( - row['newer_median_ms'] < row['older_median_ms'] for row in outliers)) - or direction.get('data-tail-older-faster') != str(sum( - row['older_median_ms'] < row['newer_median_ms'] for row in outliers)) - or not direction.get_text(' ', strip=True)): - raise ValueError(prefix + 'Moli tail conclusion disagrees with per-page medians') - for rendered, evidence in zip(case_rows, latency_diagnostic['tail_repeat']['cases']): - cells = rendered.find_all('td', recursive=False) - if (not cells[1].select_one(f'a[href$="datasets/{evidence["case_id"]}.html"]') - or any(not close(float(td['data-sort']), evidence[key]) - for td, key in zip(cells[2:], ('older_median_ms', 'newer_median_ms', - 'older_max_ms', 'newer_max_ms')))): - raise ValueError(prefix + 'Moli per-case repeat table differs from verified evidence') - conclusion_markers = (('先请求', '不能保证', '20 页') if not prefix - else ('first call', 'cannot be guaranteed', '20 pages')) - if not all(marker in section.get_text(' ', strip=True) for marker in conclusion_markers): - raise ValueError(prefix + 'Moli repeat conclusion or ordering effect is missing') - if len(section.select('.wmb-rerun-resource tbody tr')) != 2 or not section.select_one('a[href$="latency-diagnostic.json"]'): - raise ValueError(prefix + 'Moli latency rerun lacks resource evidence or per-case download') - failed_case = latency_diagnostic['failure_retest']['case_id'] - if (not section.select_one(f'a[href$="datasets/{failed_case}.html"]') - or not all(marker in section.get_text(' ', strip=True) for marker in - (('重试', '禁用页面脚本') if not prefix else ('retry', 'Disabling page-authored JavaScript')))): - raise ValueError(prefix + 'Moli failed-page retest conclusion is missing') + check_latency_diagnostic(section, latency_diagnostic, + formal_round=latency_correction['selected_round'] if latency_correction else None) home_headers = {tag.get_text(" ", strip=True) for tag in localized_home.select("#wmb-core-results th")} if teds_revision is not None: correction_label = '修正后复算' if not prefix else 'recomputed after correction' @@ -882,7 +883,7 @@ def check_sample_report(root: Path, dataset: dict | None = None, raise ValueError("Report CSS differs from the reviewed reference-shell extension") if digest(root / "assets" / "report.js") != EXPECTED_SCRIPT_SHA256: raise ValueError("Report JavaScript differs from the reviewed inspection controls") - validate_sample_content(root, authority_root=repo) + validate_sample_content(root, authority_root=authority_root or repo, binding_path=profile_path.parent / "webmainbench-content-binding.json") @@ -899,6 +900,7 @@ def sample_main(): parser = argparse.ArgumentParser(description="Check a frozen WebMainBench sample report") parser.add_argument("report", type=Path) parser.add_argument("--authority-root", type=Path) + parser.add_argument("--profile", type=Path) args = parser.parse_args() - check_sample_report(args.report, authority_root=args.authority_root) + check_sample_report(args.report, authority_root=args.authority_root, profile_path=args.profile) print("WebMainBench sample report release gate passed") diff --git a/harness/web_fetch/webmainbench_runner.py b/harness/web_fetch/webmainbench_runner.py index 1e8a591d2..3256b88f8 100644 --- a/harness/web_fetch/webmainbench_runner.py +++ b/harness/web_fetch/webmainbench_runner.py @@ -398,12 +398,14 @@ def run(args: argparse.Namespace) -> None: records = [r for r in records if r["track_id"] in selected] selection_binding = {"sha256": sha256_bytes(sample_bytes), "case_count": len(records), "scope": sample.get('scope'), "seed": sample.get('seed')} - wcxb_versions = json.loads((ROOT / 'evaluation/wcxb/current-report.json').read_text())['versions'] - # The pinned PR source predates v1.1.8 and reports 1.1.7. Its identity - # is bound separately by source commit and binary hash in the profile. - expected_versions = {"lightpanda": "0.4.1", "obscura": "0.2.2", - "moli": wcxb_versions['moli'], "moli-pr643": "1.1.7", - "chromium": "153.0.8010.47"} + if args.profile is None: + raise ValueError("WCXB execution requires an explicit --profile") + profile = json.loads(args.profile.read_text()) + expected_versions = profile.get('expected_versions') + if (not isinstance(expected_versions, dict) + or set(expected_versions) != set(profile['providers']) + or args.provider not in profile['providers']): + raise ValueError("The run profile must declare expected_versions for every participant") dataset_id = sample['dataset_id'] else: records = load_source(args.dataset, selected, args.expected_dataset_sha256) @@ -572,6 +574,7 @@ def main() -> None: parser.add_argument("--limit", type=int) parser.add_argument("--timeout", type=float, default=45.0) add_failure_retry_argument(parser) + parser.add_argument("--profile", type=Path, help="External WCXB run profile") parser.add_argument("--expected-version", help="Exact release version required for this provider") parser.add_argument( "--expected-dataset-sha256", diff --git a/package-lock.json b/package-lock.json index 765c16d17..4fbac8bbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ }, "engines": { "node": ">=20" - } + }, + "license": "Apache-2.0" }, "node_modules/boolbase": { "version": "1.0.0", diff --git a/package.json b/package.json index 6c5d10b13..4e422eb8a 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,6 @@ }, "dependencies": { "node-html-markdown": "2.0.0" - } + }, + "license": "Apache-2.0" } diff --git a/pyproject.toml b/pyproject.toml index 924a83d3f..687aa557f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,11 @@ name = "browser-harness-eval" version = "0.1.0" description = "Evaluation harness for web fetching, browser trajectory replay and autonomous agents" readme = "README.md" -license = {file = "LICENSE"} +license = "Apache-2.0" +license-files = ["LICENSE", "NOTICE", "THIRD_PARTY.md"] requires-python = ">=3.11" dependencies = [ - "lexbench-webfetch @ git+https://github.com/lexmount/lexbench-webfetch.git@5ac291a592fec849d86bbb791a80579cc7a6c2c6", + "lexbench-webfetch @ git+https://github.com/lexmount/lexbench-webfetch.git@6bd263ed98030a883e66ac75a47ac4e7fbae4ad1", "beautifulsoup4>=4.12,<5", "fastcdc==1.7.0", ] @@ -93,7 +94,7 @@ select = ["B", "E", "F", "I", "UP"] "harness/trajectory_replay/public_webarena/metrics.py" = ["F401", "I001"] [build-system] -requires = ["setuptools>=64"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] @@ -101,8 +102,10 @@ include = ["harness*"] [tool.setuptools.package-data] "harness" = ["licenses/*.txt"] +"harness.trajectory_replay" = ["messages.json"] +"harness.browser_use" = ["report-translations.json"] "harness.lexbench" = ["assets/*.js", "assets/*.cjs"] -"harness.web_fetch" = ["data/*.json", "data/latency-certifiers/*.txt", "templates/*.html", "assets/methodology/*.png", "assets/*.js", "html_to_markdown.mjs"] +"harness.web_fetch" = ["report-translations.json", "data/*.json", "data/latency-certifiers/*.txt", "templates/*.html", "assets/methodology/*.png", "assets/*.js", "html_to_markdown.mjs"] "harness.web_fetch.workers" = ["*.mts", "*.mjs", "*.c"] "harness.environments.webarena" = ["runtime/*.ini"] diff --git a/scripts/build_browser_use_replay_seeds.py b/scripts/build_browser_use_replay_seeds.py index 8a71b39bf..89ae64832 100644 --- a/scripts/build_browser_use_replay_seeds.py +++ b/scripts/build_browser_use_replay_seeds.py @@ -12,7 +12,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() - parser.add_argument("--profile", default="evaluation/browser-use/current-report.json") + parser.add_argument("--profile", required=True) parser.add_argument("--evidence", action="append", required=True) parser.add_argument("--output", required=True) return parser.parse_args() diff --git a/scripts/build_report.py b/scripts/build_report.py index 07c0b056c..a5fe8d045 100644 --- a/scripts/build_report.py +++ b/scripts/build_report.py @@ -15,8 +15,9 @@ if __name__ == '__main__': parser=argparse.ArgumentParser(description=__doc__) parser.add_argument('--evidence',type=Path) + parser.add_argument('--authority-root', type=Path, help='External authority bundle for historical full-cohort reports') parser.add_argument('--profile',type=Path, - help='current-report profile under evaluation/; requires an explicit output unless the profile declares report_output') + help='external report profile; requires an explicit output unless the profile declares report_output') parser.add_argument('--public-benchmark-research',type=Path) parser.add_argument('--output',type=Path) add_language_argument(parser) @@ -56,10 +57,17 @@ def requested_language(output, supported=SUPPORTED_REPORT_LANGUAGES): except ValueError as error: parser.error(str(error)) + if args.local_audit and (args.profile or args.wcxb or args.webmainbench_full or args.public_benchmark_research): + parser.error('--local-audit cannot be combined with a release profile, WCXB, WebMainBench, or public benchmark research') if args.profile: args.profile=(ROOT/args.profile).resolve() if not args.profile.is_absolute() else args.profile.resolve() - if not args.profile.is_relative_to((ROOT/'evaluation').resolve()): - parser.error('--profile must stay under evaluation/') + if json.loads(args.profile.read_text()).get('variant') == 'published-snapshot-audit': + if not args.evidence or not args.output or args.local_audit or args.wcxb or args.include_english: + parser.error('snapshot audit requires a separate --evidence, --output and bound --profile') + requested_language(args.output, supported=('zh',)) + from harness.web_fetch.published_snapshot import build_snapshot_audit + print(build_snapshot_audit(args.evidence, args.output, profile=args.profile)) + raise SystemExit(0) if json.loads(args.profile.read_text()).get('variant') == 'diagnostic-cohorts': args.diagnostic_cohorts=True if args.diagnostic_cohorts: @@ -82,20 +90,20 @@ def requested_language(output, supported=SUPPORTED_REPORT_LANGUAGES): show_local_status_banners=True, presentation_binding=args.presentation_binding)) raise SystemExit(0) - if sum(bool(value) for value in (args.profile, args.wcxb, args.webmainbench_full)) > 1: - parser.error('--profile, --wcxb and --webmainbench-full are mutually exclusive') + if args.wcxb and args.webmainbench_full: + parser.error('--wcxb and --webmainbench-full are mutually exclusive') if args.webmainbench_full: if not args.evidence or args.include_english: parser.error('--webmainbench-full requires --evidence; this historical report has no English edition') output=args.output or ROOT/'reports/webmainbench-full' requested_language(output, supported=('zh',)) - print(build_webmainbench_full_report(args.evidence,output)) + print(build_webmainbench_full_report(args.evidence,output,authority_root=args.authority_root)) raise SystemExit(0) if args.public_benchmark_research: if args.evidence: parser.error('--evidence and --public-benchmark-research are mutually exclusive') - if args.output is None and json.loads((ROOT/'evaluation/current-report.json').read_text()).get('comparison_kind')=='webmainbench_frozen_html_to_markdown': - parser.error('The independent public-benchmark report now contains reviewed measurements; a research-only draft requires an explicit separate --output') + if args.output is None: + parser.error('A research companion requires an explicit separate --output') output=args.output or ROOT/'reports/webfetch-public-benchmarks' print(build_public_benchmark_research( args.public_benchmark_research,output,include_english=args.include_english, language=requested_language(output))) @@ -103,7 +111,7 @@ def requested_language(output, supported=SUPPORTED_REPORT_LANGUAGES): if args.profile: profile_path=args.profile else: - profile_path=ROOT/('evaluation/wcxb/current-report.json' if args.wcxb else 'evaluation/current-report.json') + parser.error('Select an external report instance with --profile') profile=json.loads(profile_path.read_text()) if args.wcxb and profile.get('comparison_kind')!='wcxb_frozen_html_to_markdown': parser.error('The WCXB profile does not describe the WCXB matrix') @@ -129,4 +137,4 @@ def requested_language(output, supported=SUPPORTED_REPORT_LANGUAGES): output=(args.output or (ROOT/declared_output if declared_output else ROOT/'reports/webfetch')) print(build_report(evidence,output, include_english=args.include_english, language=requested_language(output), - authored_interpretation=True)) + authored_interpretation=True, profile_path=profile_path)) diff --git a/scripts/moli_report/BUILD.md b/scripts/moli_report/BUILD.md index f974503db..4dd60db62 100644 --- a/scripts/moli_report/BUILD.md +++ b/scripts/moli_report/BUILD.md @@ -2,7 +2,7 @@ The Moli comparison report adapter lives in `harness/lexbench/`; this directory contains its command entries and reader assets. Measured evidence stays outside Git under `BROWSER_EVAL_DATA_HOME` (default: `~/.browser-eval`). It does not use the Web Fetch publication target. -The frozen Linux runner inputs are read from `LEXBENCH_REPORT_RUNNER_ROOT` (default: `$BROWSER_EVAL_DATA_HOME/work/moli-1.1.8-failures-linux`). Report generation verifies their recorded hashes without importing or executing their Python files. `harness/resource_calibration.py` verifies complete profiler-off and profiler-on pairs and their shared experimental provenance. Its calculation is derived from the frozen Lexbench source, with Apache-2.0 attribution and license retained in the repository and installation package. All seven resource configurations use Linux ARM64 release binaries, PSS and process-tree CPU measurements in Docker on the Mac host. +The frozen Linux runner inputs are read from the selected profile’s `paths.runner`; `LEXBENCH_REPORT_RUNNER_ROOT` can override that path. Report generation verifies their recorded hashes without importing or executing their Python files. `harness/resource_calibration.py` verifies complete profiler-off and profiler-on pairs and their shared experimental provenance. Its calculation is derived from the frozen Lexbench source, with Apache-2.0 attribution and license retained in the repository and installation package. All seven resource configurations use Linux ARM64 release binaries, PSS and process-tree CPU measurements in Docker on the Mac host. The report profile and chart payload have separate schemas. In `moli_report_profile/1`, resource `tasks` and `attempts_per_task` map to the chart's `total_tasks` and `calls_per_task`; `chart_tasks` and `chart_calls` retain their names. The builder validates the measured populations before constructing the chart payload. Do not pass the profile directly to the plotting API. @@ -10,6 +10,7 @@ Build from validated fixed-layout functional summaries and the complete resource ```sh uv run --frozen python scripts/moli_report/build.py \ + --profile /path/to/report-profile.json \ --output /tmp/moli-report-release \ --fixed-summary 119_off /path/to/patched-off-summary.json \ --fixed-summary 119_on /path/to/patched-on-summary.json \ @@ -21,9 +22,9 @@ uv run --frozen python scripts/moli_report/verify.py --site /tmp/moli-report-rel The default build generates Chinese pages and charts only. Add `--include-english` to generate English explicitly. `report-languages.json` records the editions used by verification, packaging and browser checks. Without English, both English entry paths show a notice with a return link. Translation sources remain available for manual generation. -Choose a new output directory for each build; the builder does not delete existing output. `--vendor-dir` selects the retained Mermaid distribution and license. Their hashes are checked before copying. The default location is the original report asset directory under the data home; no vendored executable or measured output is committed here. +Choose a new output directory for each build; the builder does not delete existing output. `--vendor-dir` selects the retained Mermaid distribution and license. Their hashes are checked before copying. The profile supplies its location under the data home; no vendored executable or measured output is committed here. -`--profile` selects the report profile. `--cohort` selects the functional cohort and its adjacent `protocol.json` and `certification.json`; unless supplied separately with `--summary`, `report-runs.json` comes from the same directory. Importing the adapter or requesting command help does not read private evidence. Evidence validation remains enabled under Python optimization. +`--profile` is required and selects the report instance. Its `paths` object owns the cohort, runner, output, vendor assets, correction inputs and version comparison inputs. Relative evidence paths use the data home; the template source is relative to the repository. Explicit CLI paths override the corresponding profile entries. Chart filenames use the shared `comparison-` prefix. `--cohort` selects the functional cohort and its adjacent `protocol.json` and `certification.json`; unless supplied separately with `--summary`, `report-runs.json` comes from the same directory. Importing the adapter or requesting command help does not read private evidence. Evidence validation remains enabled under Python optimization. The base three-configuration diagnostic report and partial fixed-layout additions retain their method summary and interpretation navigation. The 1.1.9 comparison chapter is rendered only when both compared configurations exist. This does not relax the complete method matrix required by `--release`. @@ -39,7 +40,7 @@ uv run --frozen python scripts/moli_report/run.py \ The default operation validates inputs and prints the Docker command. `--execute` starts the selected run; it preserves existing runs and rechecks frozen inputs afterward. The protocol and cohort must describe the same complete 372-case, three-attempt scope. Report bundles include this module as `reproduction/run.py`, with adjacent `cohort.json` and `protocol.json` as defaults; install Browser Eval before using it. Python optimization does not disable input checks. Historical source and measured-call bindings are unchanged. -The four 1.1.9 resource configurations are release builds bound by `resource_builds` in `evaluation/moli/current-report.json`. Functional evidence binds its own binary identity. Do not relabel retained functional outcomes as a new build's measured accuracy. +The four 1.1.9 resource configurations are release builds bound by `resource_builds` in `/path/to/report-profile.json`. Functional evidence binds its own binary identity. Do not relabel retained functional outcomes as a new build's measured accuracy. `uv run --frozen python scripts/moli_report/run_linux_resource_profiles.py --protocol /path/to/frozen-protocol.json --output /path/to/new-evidence-directory` executes the unchanged Lexbench driver and resource collector. Freeze 557 task definitions, fixtures, runner files, four binary hashes, five attempts, jobs=1, the seed and the 250 ms interval before execution. Each configuration has an independent container; all runs execute serially. The official profiler-off calibration precedes the profiler-on measurement. Resource values come from the latter. The builder verifies all 11,140 measured calls and their calibration pairs, checks the frozen 1,045-call chart population, recomputes aggregates and rejects missing or changed evidence. Keep all measured files outside Git. diff --git a/scripts/moli_report/app.js b/scripts/moli_report/app.js index cdf631598..b60eb06d4 100644 --- a/scripts/moli_report/app.js +++ b/scripts/moli_report/app.js @@ -2,192 +2,19 @@ const root = document.documentElement, base = root.dataset.base || "", zh = root.lang.startsWith("zh"); -const D = { - zh: { - nav: { - home: "总览", - dataset: "评测集", - method: "评测方法", - analysis: "结果分析", - results: "逐题结果", - review: "证据与可靠性", - guide: "报告解读", - reproduce: "环境与复现", - }, - modes: { off: "无布局", on: "开布局" }, - strata: { - raw_read: "原始 CDP 读取", - raw_geometry_input: "几何与坐标输入", - playwright: "Playwright 常规操作", - puppeteer: "Puppeteer 常规操作", - agent_tool: "Agent 工具", - multi_step: "多步交互", - agent_scenario: "Agent 场景", - }, - pass: "通过", - fail: "失败", - all: "全部", - open: "查看", - evidence: "证据", - missing: "暂无证据", - loading: "正在载入…", - error: "数据读取失败", - noHuman: "未进行独立人工逐次复核", - methodLimit: - "覆盖 Moli 0.1.1 的全部 372 个所选用例;结果适用于这一失败子集。", - more: "查看详情", - task: "任务", - mode: "模式", - attempt: "第 {n} 次", - status: "状态", - duration: "耗时", - cpu: "CPU 时间", - pss: "结束 PSS", - traffic: "站点下行", - failure: "程序错误", - source: "脱敏证据", - search: "搜索任务、驱动或程序错误", - relation: "结果关系", - stratum: "任务类型", - bothPass: "两种模式均通过", - gain: "开启后新增通过用例", - mixed: "部分通过,次数相同", - bothFail: "两种模式均失败", - loss: "开启后退步用例", - back: "返回逐题结果", - close: "关闭", - unavailable: "未提供", - start: "查看逐题结果", - method: "查看方法", - metricNote: - "P50 是中位数;P95 是第 95 百分位。耗时为驱动执行与验收的墙钟时间,不含浏览器启动、外围目标检查及最终证据落盘。", - winner: "较低", - higher: "较高", - scope: "适用范围", - observed: "观测到的采样最大值", - filterFail: "涉及失败或变化", - methodIntro: - "题目与验收预先冻结,同一构建的两种布局配置只相差是否启用 --layout。默认模式使用低成本模拟几何;--layout 启用按需真实布局,并非持续绘制。可选图片等资源抓取由另一开关控制,未改变。功能与资源独立测量,任务范围和执行次数分别见对应配置表。", - analysisIntro: "按相同任务与执行次数配对。成功变化和资源代价分开解读。", - guideIntro: - "从 Agent 使用者角度,先看能否完成,再看失败等待和完成任务的资源。", - details: "展开证据", - calibration: "采样校准", - resultCount: "显示 {n} / {total} 个任务", - rawContract: "原始任务合同", - noRows: "没有符合筛选条件的任务。", - }, - en: { - nav: { - home: "Overview", - dataset: "Dataset", - method: "Method", - analysis: "Analysis", - results: "Task results", - review: "Evidence quality", - guide: "Interpretation", - reproduce: "Environment", - }, - modes: { off: "Layout off", on: "Layout on" }, - strata: { - raw_read: "Raw CDP reading", - raw_geometry_input: "Geometry and input", - playwright: "Playwright operations", - puppeteer: "Puppeteer operations", - agent_tool: "Agent tools", - multi_step: "Multi-step flows", - agent_scenario: "Agent scenarios", - }, - pass: "Pass", - fail: "Fail", - all: "All", - open: "Open", - evidence: "Evidence", - missing: "No evidence available", - loading: "Loading…", - error: "Could not load data", - noHuman: "No independent per-call review", - methodLimit: - "This cohort includes all 372 cases selected from Moli 0.1.1 failures; results apply to this failure subset.", - more: "View details", - task: "Task", - mode: "Mode", - attempt: "Attempt {n}", - status: "Status", - duration: "Duration", - cpu: "CPU time", - pss: "Ending PSS", - traffic: "Fixture downlink", - failure: "Program error", - source: "Sanitized evidence", - search: "Search task, driver, or program error", - relation: "Outcome relation", - stratum: "Task family", - bothPass: "Both modes pass", - gain: "Cases gained with layout", - mixed: "Equal partial passes", - bothFail: "Both modes fail", - loss: "Cases lost with layout", - back: "Back to task results", - close: "Close", - unavailable: "Unavailable", - start: "Explore task results", - method: "Read methodology", - metricNote: - "P50 is the median and P95 the 95th percentile. Duration covers driver execution and grading, excluding browser startup, outer target checks and final evidence writing.", - winner: "Lower", - higher: "Higher", - scope: "Scope", - observed: "Observed sampled maximum", - filterFail: "Failures or changes", - methodIntro: - "Task contracts were frozen in advance. Within each build, the two layout configurations differ only in Moli's --layout flag. The default uses low-cost mock geometry; --layout enables real geometry on demand rather than continuous painting. Optional image and media fetching is a separate control and was unchanged. Functional and resource experiments declare their task populations and attempt counts separately in the configuration tables.", - analysisIntro: - "Pair the same task and attempt. Outcome changes and resource costs are interpreted separately.", - guideIntro: - "For an agent user, completion comes first, followed by failure waiting time and cost on completed tasks.", - details: "Expand evidence", - calibration: "Profiling calibration", - resultCount: "Showing {n} / {total} tasks", - rawContract: "Raw task contract", - noRows: "No tasks match these filters.", - }, +const D = JSON.parse(document.getElementById("report-messages").textContent); + + +const T = D[zh ? "zh" : "en"]; +const message = (key, values = {}) => { + const text = T.messages[key]; + if (typeof text !== "string") throw Error(`Missing report message: ${key}`); + return text.replace(/\{(value\d+)\}/g, (_, name) => { + if (!Object.hasOwn(values, name) || values[name] === undefined) + throw Error(`Missing report message parameter: ${key}.${name}`); + return String(values[name]); + }); }; -Object.assign(D.zh.strata, { - node_cdp_probe: "Node CDP", - raw_cdp: "Raw CDP", - tool_agent_browser: "agent-browser", - thin_cdp_use: "cdp-use", - thin_chromedp: "chromedp", - thin_chrome_remote_interface: "chrome-remote-interface", - thin_ferrum: "Ferrum", - mcp_chrome_devtools: "Chrome DevTools MCP", - thin_chromiumoxide: "Chromiumoxide", - framework_puppeteer: "Puppeteer", - framework_playwright: "Playwright", - thin_pydoll: "Pydoll", - thin_rod: "Rod", - framework_stagehand: "Stagehand", - webdriver_selenium: "Selenium", -}); -Object.assign(D.en.strata, { - node_cdp_probe: "Node CDP", - raw_cdp: "Raw CDP", - tool_agent_browser: "agent-browser", - thin_cdp_use: "cdp-use", - thin_chromedp: "chromedp", - thin_chrome_remote_interface: "chrome-remote-interface", - thin_ferrum: "Ferrum", - mcp_chrome_devtools: "Chrome DevTools MCP", - thin_chromiumoxide: "Chromiumoxide", - framework_puppeteer: "Puppeteer", - framework_playwright: "Playwright", - thin_pydoll: "Pydoll", - thin_rod: "Rod", - framework_stagehand: "Stagehand", - webdriver_selenium: "Selenium", -}); -const T = zh ? D.zh : D.en; let M = null, dialog = document.getElementById("evidence-dialog"), trigger = null; @@ -207,41 +34,27 @@ const fmt = (v, d = 0) => minimumFractionDigits: d, }); const population = () => - zh - ? `同一组预先固定的 ${M.task_count} 个任务,每种模式各运行 ${M.protocol.k_runs} 次,共 ${M.attempts_per_mode} 次调用。` - : `The same ${M.task_count} preselected tasks, ${M.protocol.k_runs} calls each and ${M.attempts_per_mode} calls per mode.`; + message("the_same_preselected_tasks_calls_each_and_calls_per_mode", {value0: (M.task_count), value1: (M.protocol.k_runs), value2: (M.attempts_per_mode)}); const casePopulation = () => - zh - ? `通过数按 ${M.task_count} 个用例统计:每题已记录的 ${M.protocol.k_runs} 次无采样执行全部通过,才计为 1 个通过用例。` - : `Pass counts use ${M.task_count} cases: a case passes only when all ${M.protocol.k_runs} recorded unprofiled attempts pass.`; + message("pass_counts_use_cases_a_case_passes_only_when_all_recorded_unprof", {value0: (M.task_count), value1: (M.protocol.k_runs)}); const caseVerdict = (n) => statusChip(n === M.protocol.k_runs ? "pass" : "fail"); const metricPopulation = () => - zh - ? `全部 ${M.attempts_per_mode} 次调用,成功与失败都计入;平均、P50 和 P95 使用同一组调用。` - : `All ${M.attempts_per_mode} predeclared calls, including failures; Mean, P50 and P95 use the same population.`; -const reportSubtitle = () => zh - ? `比较 ${M.functional_methods.length} 种固定配置在 ${M.task_count} 个所选用例上的成功率,并展示独立资源实验。` - : `Compare ${M.functional_methods.length} fixed configurations on ${M.task_count} selected cases, with separate resource experiments.`; + message("all_predeclared_calls_including_failures_mean_p50_and_p95_use_the", {value0: (M.attempts_per_mode)}); +const reportSubtitle = () => message("compare_fixed_configurations_on_selected_cases_with_separate_reso", {value0: (M.functional_methods.length), value1: (M.task_count)}); const reportVersion = () => { const versions = [...new Set((M?.functional_methods || []).flatMap((method) => [...(method.label_en || method.label || "").matchAll(/Moli\s+(\d+\.\d+\.\d+)/g)].map((match) => match[1]), ))]; return versions.length ? `Moli ${versions.join(" / ")}` : "Moli"; }; -const reportTitle = () => zh - ? `${reportVersion()} · 成功率与资源对比` - : `${reportVersion()} · Compatibility and resources`; +const reportTitle = () => message("compatibility_and_resources", {value0: (reportVersion())}); const datasetIntro = () => - zh - ? `依据 Moli 0.1.1 原始结果选取全部 372 个失败用例,覆盖 15 类驱动;不使用结果决定入选。` - : `All 372 cases selected from Moli 0.1.1 failures are included across 15 driver types, without selection based on current outcomes.`; + message("all_372_cases_selected_from_moli_0_1_1_failures_are_included_acro"); const reproduceIntro = () => { const platforms = M.linux_resource_profiles ? "Linux" : M.macos_resource || M.official_resource ? "Linux / macOS" : "Linux"; - return zh - ? `${M.task_count} 个功能用例;直接测试的用例每题记录 ${M.protocol.k_runs} 次执行。各配置实际测试的范围和沿用既有证据的范围见表。本报告为诊断实验。资源测量环境:${platforms}。功能和资源测试各自记录浏览器的二进制哈希;通过率对应功能测试使用的构建。` - : `${M.task_count} functional cases; directly tested cases have ${M.protocol.k_runs} recorded attempts each. The configuration table identifies directly tested cases and verdicts retained from existing evidence. This is a diagnostic experiment. Resource environments: ${platforms}. Functional and resource experiments bind their binary identities separately; success rates apply to the corresponding functional build.`; + return message("functional_cases_directly_tested_cases_have_recorded_attempts_eac", {value0: (M.task_count), value1: (M.protocol.k_runs), value2: (platforms)}); }; const ms = (v) => v === null || v === undefined @@ -299,45 +112,33 @@ function functionalMethodSummary() { : ""; let policy = row.policy === "fixed_on" - ? zh - ? "全程开启" - : "Always on" - : zh - ? "全程关闭" - : "Always off"; + ? message("always_on") + : message("always_off"); let scope = row.inherited_cases - ? `${row.direct_cases} ${zh ? "题实测" : "direct"} + ${row.inherited_cases} ${zh ? "题继承共同失败关系" : "shared failures"}` - : `${row.direct_cases} ${zh ? "题实测" : "direct cases"}`; + ? `${row.direct_cases} ${message("direct")} + ${row.inherited_cases} ${message("shared_failures")}` + : `${row.direct_cases} ${message("direct_cases")}`; let evidence = row.evidence - ? `
` + ? `
` : ""; - return `${index + 1} ${esc(zh ? row.label : row.label_en)}${resource ? `
${esc(resource.platform === "Linux" ? "Docker · Linux" : "macOS")}${row.id === "114_off" ? (zh ? " · 基线" : " · baseline") : ""}
` : ""}${evidence}${esc(policy)}${esc(scope)}${fmt(row.final_passed_cases)}/${total} (${fmt((row.final_passed_cases / total) * 100, 1)}%)${signed(((row.final_passed_cases - rows[0].final_passed_cases) / total) * 100, 2, zh ? " 个百分点" : " pp")}${fmt(row.physical_calls || (row.id === "114_off" ? 3 : total * 3))}${resourceCells}`; + return `${index + 1} ${esc(zh ? row.label : row.label_en)}${resource ? `
${esc(resource.platform === "Linux" ? "Docker · Linux" : "macOS")}${row.id === "114_off" ? (message("baseline")) : ""}
` : ""}${evidence}${esc(policy)}${esc(scope)}${fmt(row.final_passed_cases)}/${total} (${fmt((row.final_passed_cases / total) * 100, 1)}%)${signed(((row.final_passed_cases - rows[0].final_passed_cases) / total) * 100, 2, message("pp"))}${fmt(row.physical_calls || (row.id === "114_off" ? 3 : total * 3))}${resourceCells}`; }); return section( - zh - ? `${rows.length} 种固定配置的成功率` - : `Success rates for ${rows.length} fixed configurations`, + message("success_rates_for_fixed_configurations", {value0: (rows.length)}), M.resource_metrics - ? zh - ? `${total} 题,每题 3/3 通过才算成功;资源列使用固定 209 题样本,统一为 Linux 进程树 PSS 与 CPU 时间。基线:1.1.4 关闭 layout;绿色为唯一最优值。` - : `${total} cases; all 3 attempts must pass. Resources use the fixed 209-task sample and Linux process-tree PSS and CPU time. Baseline: 1.1.4 layout off; green marks each unique optimum.` - : zh - ? `${total} 题,每题 3/3 通过才算成功;资源列使用固定 209 题样本。基线:1.1.4 关闭 layout。— 表示执行环境不同,不计算资源涨幅;绿色为同环境唯一最优值。` - : `${total} cases; all 3 attempts must pass. Resources use the fixed 209-task sample. Baseline: 1.1.4 layout off. — denotes incompatible execution environments; green marks the unique optimum within each environment.`, + ? message("cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s", {value0: (total)}) + : message("cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s_49ab3d", {value0: (total)}), table( [ - zh ? "方法" : "Method", - zh ? "布局" : "Layout", - zh ? "证据范围" : "Evidence scope", - zh ? "通过用例" : "Passed cases", - zh ? "通过率变化" : "Success-rate change", - zh ? "调用数" : "Calls", + message("method"), + message("layout"), + message("evidence_scope"), + message("passed_cases"), + message("success_rate_change"), + message("calls"), ...(resourceRows.length ? [ - zh - ? "峰值内存 P50 / 基线变化" - : "Peak memory P50 / baseline change", - zh ? "CPU 均值 / 基线变化" : "Mean CPU / baseline change", + message("peak_memory_p50_baseline_change"), + message("mean_cpu_baseline_change"), ] : []), ], @@ -346,14 +147,16 @@ function functionalMethodSummary() { ), ); } +function configurationLabel(id) { + const method = M.functional_methods.find((row) => row.id === id); + return method ? (zh ? method.label : method.label_en) : id; +} function combinedSummaryChart() { - const chart = `moli-all-success-resources-${zh ? "zh" : "en"}.svg`; - return `
${esc(zh ? `${M.functional_methods.length} 种固定配置的成功率与资源,顺序与总表一致,以 1.1.4 关闭布局为基线` : `${M.functional_methods.length} fixed configurations, aligned with the summary table; baseline: 1.1.4 layout off`)}
`; + const chart = `comparison-all-success-resources-${zh ? "zh" : "en"}.svg`; + return `
${esc(message(
`; } function resourceBuildNote() { - return zh - ? "资源按执行环境分别比较。功能和资源测试各自记录浏览器构建;通过率对应功能测试使用的构建,哈希和证据见评测方法。" - : "Resources are compared within each execution environment. Functional and resource experiments bind build identities separately; success rates apply to the corresponding functional build. See the method page for build identities and evidence."; + return message("resources_are_compared_within_each_execution_environment_function"); } function macosResourceMethods() { let result = []; @@ -382,20 +185,20 @@ function candidateResourceSection() { scopes = [ [ "all_predeclared_calls", - zh ? "557 题全部调用" : "All calls on 557 tasks", + message("all_calls_on_557_tasks"), 2785, ], [ "frozen_historical_common_pass_calls", - zh ? "图表样本:209 题" : "Chart sample: 209 tasks", + message("chart_sample_209_tasks"), 1045, ], ]; let specs = [ - ["rss_peak_mib", "mean", zh ? "峰值 RSS 均值" : "Peak RSS mean", "MiB"], - ["rss_peak_mib", "p50", zh ? "峰值 RSS P50" : "Peak RSS P50", "MiB"], - ["rss_peak_mib", "p95", zh ? "峰值 RSS P95" : "Peak RSS P95", "MiB"], - ["cpu_time_ms", "mean", zh ? "CPU 均值" : "CPU mean", "ms"], + ["rss_peak_mib", "mean", message("peak_rss_mean"), "MiB"], + ["rss_peak_mib", "p50", message("peak_rss_p50"), "MiB"], + ["rss_peak_mib", "p95", message("peak_rss_p95"), "MiB"], + ["cpu_time_ms", "mean", message("cpu_mean"), "ms"], ["cpu_time_ms", "p50", zh ? "CPU P50" : "CPU P50", "ms"], ["cpu_time_ms", "p95", zh ? "CPU P95" : "CPU P95", "ms"], ]; @@ -410,7 +213,7 @@ function candidateResourceSection() { let winners = methods.filter( (x) => x.config.metrics[scope][metric][stat] === minFor(metric, stat), ); - return winners.length === 1 ? winners[0].label : zh ? "并列" : "Tie"; + return winners.length === 1 ? winners[0].label : message("tie"); }; let rows = methods.map((item) => { const reference = @@ -423,14 +226,14 @@ function candidateResourceSection() { return `${esc(item.label)}${fmt(calls)}${specs.map(([metric, stat, label, unit]) => `${fmt(m[metric][stat], 2)} ${unit}`).join("")}${signed((m.rss_peak_mib.p50 / b.rss_peak_mib.p50 - 1) * 100, 2)}${signed((m.cpu_time_ms.mean / b.cpu_time_ms.mean - 1) * 100, 2)}`; }); return ( - `

${esc(title)}

${esc(zh ? `峰值 RSS P50 最低:${leader("rss_peak_mib", "p50")};单次 CPU 均值最低:${leader("cpu_time_ms", "mean")}。资源最低与通过率最高分别判断。` : `Lowest peak RSS P50: ${leader("rss_peak_mib", "p50")}; lowest mean CPU per call: ${leader("cpu_time_ms", "mean")}. Resource minima and task success are evaluated separately.`)}

` + + `

${esc(title)}

${esc(message("lowest_peak_rss_p50_lowest_mean_cpu_per_call_resource_minima_and_", {value0: (leader("rss_peak_mib", "p50")), value1: (leader("cpu_time_ms", "mean"))}))}

` + table( [ - zh ? "方法" : "Method", - zh ? "调用数" : "Calls", + message("method"), + message("calls"), ...specs.map((x) => x[2]), - zh ? "峰值 RSS P50 变化" : "Peak RSS P50 change", - zh ? "CPU 均值变化" : "CPU mean change", + message("peak_rss_p50_change"), + message("cpu_mean_change"), ], rows, "candidate-resource-table", @@ -438,46 +241,42 @@ function candidateResourceSection() { ); }); const referenceLabel = methods.some((x) => x.id === "119_official_off") - ? zh - ? "相同 layout 设置的 Moli 1.1.9" - : "Moli 1.1.9 with the same layout setting" + ? message("moli_1_1_9_with_the_same_layout_setting") : baseline.label; - let intro = zh - ? `资源实验与 372 题成功率测试分开:每个配置固定 layout,557 题各执行 5 次。P50 是中位数,P95 是第 95 百分位;每组统计包含同一批成功与失败调用。变化基准:${referenceLabel}。macOS 测量进程树 RSS;Linux PSS 另表展示。` - : `Resources are measured separately from success on 372 cases: each configuration fixes layout and runs 557 tasks five times. P50 is the median and P95 the 95th percentile; every metric in a scope includes the same passing and failing calls. Change baseline: ${referenceLabel}. macOS measures process-tree RSS; Linux PSS is shown separately.`; + let intro = message("resources_are_measured_separately_from_success_on_372_cases_each_", {value0: (referenceLabel)}); let evidence = methods .filter((x, i, a) => a.findIndex((y) => y.resource === x.resource) === i) .map( (x) => - ``, + ``, ) .join(" "); if (M.resource_matrix) { const item = M.resource_matrix.evidence; - evidence += ` `; + evidence += ` `; } return section( - zh ? "Moli 1.1.9 资源统计" : "Moli 1.1.9 resource statistics", + message("moli_1_1_9_resource_statistics"), intro, tables.join("") + - `

${esc(zh ? "四组均使用 release 构建,共享同一主机,各自保留独立浏览器进程和采样。任务调用按配置轮换、串行执行,只统计当前配置的进程树。CPU 为用户态与内核态时间之和。" : "All four release configurations share one host and retain separate browser processes and samplers. Task calls run serially in rotating order; each measurement covers only its own process tree. CPU is user plus system time.")}

${evidence}
`, + `

${esc(message("all_four_release_configurations_share_one_host_and_retain_separat"))}

${evidence}
`, ); } function unifiedResourceSection() { const methods = M.functional_methods; const specs = [ - ["pss_peak_mib", "mean", zh ? "峰值 PSS 均值" : "Peak PSS mean", "MiB"], - ["pss_peak_mib", "p50", zh ? "峰值 PSS P50" : "Peak PSS P50", "MiB"], - ["pss_peak_mib", "p95", zh ? "峰值 PSS P95" : "Peak PSS P95", "MiB"], - ["cpu_time_ms", "mean", zh ? "CPU 均值" : "CPU mean", "ms"], + ["pss_peak_mib", "mean", message("peak_pss_mean"), "MiB"], + ["pss_peak_mib", "p50", message("peak_pss_p50"), "MiB"], + ["pss_peak_mib", "p95", message("peak_pss_p95"), "MiB"], + ["cpu_time_ms", "mean", message("cpu_mean"), "ms"], ["cpu_time_ms", "p50", zh ? "CPU P50" : "CPU P50", "ms"], ["cpu_time_ms", "p95", zh ? "CPU P95" : "CPU P95", "ms"], ]; let content = [ - ["all_predeclared_calls", zh ? "557 题全部调用" : "All calls on 557 tasks"], + ["all_predeclared_calls", message("all_calls_on_557_tasks")], [ "frozen_historical_common_pass_calls", - zh ? "图表样本:209 题" : "Chart sample: 209 tasks", + message("chart_sample_209_tasks"), ], ] .map(([scope, title]) => { @@ -495,9 +294,7 @@ function unifiedResourceSection() { ? zh ? found[0].label : found[0].label_en - : zh - ? "并列" - : "Tie"; + : message("tie"); }; const baseline = M.resource_metrics["114_off"][scope]; const rows = methods.map((method) => { @@ -512,14 +309,14 @@ function unifiedResourceSection() { return `${esc(zh ? method.label : method.label_en)}${fmt(values.pss_peak_mib.n)}${cells}${signed((values.pss_peak_mib.p50 / baseline.pss_peak_mib.p50 - 1) * 100, 2)}${signed((values.cpu_time_ms.mean / baseline.cpu_time_ms.mean - 1) * 100, 2)}`; }); return ( - `

${esc(title)}

${esc(zh ? `峰值 PSS P50 最低:${winnerName("pss_peak_mib", "p50")};单次 CPU 均值最低:${winnerName("cpu_time_ms", "mean")}。资源消耗与通过率分别比较。` : `Lowest peak PSS P50: ${winnerName("pss_peak_mib", "p50")}; lowest mean CPU per call: ${winnerName("cpu_time_ms", "mean")}. Resource consumption and success are compared separately.`)}

` + + `

${esc(title)}

${esc(message("lowest_peak_pss_p50_lowest_mean_cpu_per_call_resource_consumption", {value0: (winnerName("pss_peak_mib", "p50")), value1: (winnerName("cpu_time_ms", "mean"))}))}

` + table( [ - zh ? "方法" : "Method", - zh ? "调用数" : "Calls", + message("method"), + message("calls"), ...specs.map((s) => s[2]), - zh ? "PSS P50 变化" : "PSS P50 change", - zh ? "CPU 均值变化" : "Mean CPU change", + message("pss_p50_change"), + message("mean_cpu_change"), ], rows, "unified-resource-table", @@ -530,40 +327,29 @@ function unifiedResourceSection() { content += `

${[ [ M.resource_metrics_source, - zh ? "1.1.4 / 1.1.8 资源证据" : "1.1.4 / 1.1.8 resource evidence", + message("1_1_4_1_1_8_resource_evidence"), ], [ - M.linux_resource_profiles.evidence, - zh ? "1.1.9 测量与校准证据" : "1.1.9 measurements and calibration", + M.linux_resource_profiles?.evidence, + message("1_1_9_measurements_and_calibration"), ], ] + .filter(([evidence]) => evidence?.url && evidence?.sha256) .map( ([evidence, label]) => ``, ) .join(" ")}

`; return section( - zh ? "七种配置的资源统计" : "Resource statistics for seven configurations", - zh - ? "各指标使用同一批成功与失败调用;P50 为中位数,P95 为第 95 百分位。全部变化以 1.1.4 关闭 layout 为基准。" - : "All metrics include the same passing and failing calls. P50 is the median and P95 the 95th percentile. Every change uses 1.1.4 with layout off as its baseline.", + message("resource_statistics_for_seven_configurations"), + message("all_metrics_include_the_same_passing_and_failing_calls_p50_is_the"), content, ); } function unifiedResourceMethodology() { - const paragraphs = zh - ? [ - "资源测试独立使用 557 题,每题执行五次,每种配置共 2,785 次调用。图和总表使用事先固定的 209 题、1,045 次调用;完整资源表另列全部 557 题。每个统计范围都保留成功与失败调用。", - "七种配置都在 Mac 主机上的 Docker Linux 环境中测量,使用 Linux ARM64 release 二进制和同一资源采集方法。PSS 通过 /proc/PID/smaps_rollup 采集并按进程树求和,CPU 为该进程树的用户态与内核态 CPU 时间之和。250 ms 采集间隔,jobs=1,各配置串行执行。", - "官方流程先执行 profiler-off 校准,再执行 profiler-on 测量;报告资源值来自后者,校准用于检查采集器影响。P50 是中位数,P95 是第 95 百分位。全部资源变化以 1.1.4 关闭 layout 为基线,表示对应样本统计值的差异。官网 71 MiB 不参与这七组计算。", - ] - : [ - "Resources use a separate 557-task experiment with five attempts per task, or 2,785 calls per configuration. The chart and overview table use 1,045 calls on 209 tasks fixed before execution; the detailed resource tables also show all 557 tasks. Each scope retains passing and failing calls.", - "All seven configurations are measured in Docker Linux on the Mac host, using Linux ARM64 release binaries and the same resource collection method. PSS is read from /proc/PID/smaps_rollup and summed over the process tree; CPU is the tree's user plus system CPU time. Sampling uses a 250 ms interval and jobs=1, with configurations executed serially.", - "The official procedure runs profiler-off calibration followed by profiler-on measurement. Reported resources come from the latter; calibration checks observer effect. P50 is the median and P95 the 95th percentile. Every resource change uses 1.1.4 with layout off as the baseline and describes the corresponding sample statistic. The website's 71 MiB value is excluded from these seven configurations.", - ]; + const paragraphs = T.resource_methodology; return section( - zh ? "资源测量与可比范围" : "Resource measurement and comparability", + message("resource_measurement_and_comparability"), "", paragraphs.map((p) => `

${esc(p)}

`).join(""), ); @@ -574,28 +360,28 @@ function resourceGroups() { rx = M.network_rx, cold = M.cold_start; let cpu = table( - [zh ? "CPU 指标" : "CPU metric", T.modes.off, T.modes.on], + [message("cpu_metric"), T.modes.off, T.modes.on], [ metricRow( - zh ? "单次 CPU 均值" : "Mean per-call CPU", + message("mean_per_call_cpu"), off.cpu_ms_all.mean, on.cpu_ms_all.mean, ms, ), metricRow( - zh ? "累计 CPU 时间" : "Total CPU time", + message("total_cpu_time"), off.cpu_ms_all.sum, on.cpu_ms_all.sum, (v) => fmt(v / 1000, 2) + " s", ), metricRow( - zh ? "单次 CPU P50" : "Per-call CPU P50", + message("per_call_cpu_p50"), off.cpu_ms_all.p50, on.cpu_ms_all.p50, ms, ), metricRow( - zh ? "单次 CPU P95" : "Per-call CPU P95", + message("per_call_cpu_p95"), off.cpu_ms_all.p95, on.cpu_ms_all.p95, ms, @@ -603,28 +389,28 @@ function resourceGroups() { ], ); let memory = table( - [zh ? "内存指标" : "Memory metric", T.modes.off, T.modes.on], + [message("memory_metric"), T.modes.off, T.modes.on], [ metricRow( - zh ? "观测 PSS 最大值均值" : "Mean observed PSS maximum", + message("mean_observed_pss_maximum"), off.pss_peak_mib_all.mean, on.pss_peak_mib_all.mean, (v) => fmt(v, 2) + " MiB", ), metricRow( - zh ? "观测 PSS 最大值 P50" : "Observed PSS maximum P50", + message("observed_pss_maximum_p50"), off.pss_peak_mib_all.p50, on.pss_peak_mib_all.p50, (v) => fmt(v, 2) + " MiB", ), metricRow( - zh ? "观测 PSS 最大值 P95" : "Observed PSS maximum P95", + message("observed_pss_maximum_p95"), off.pss_peak_mib_all.p95, on.pss_peak_mib_all.p95, (v) => fmt(v, 2) + " MiB", ), metricRow( - zh ? "PSS 增量 P50" : "PSS increase P50", + message("pss_increase_p50"), off.pss_delta_mib_all.p50, on.pss_delta_mib_all.p50, (v) => fmt(v, 2) + " MiB", @@ -633,31 +419,31 @@ function resourceGroups() { ); let network = table( [ - zh ? "站点应用层流量" : "Fixture application traffic", + message("fixture_application_traffic"), T.modes.off, T.modes.on, ], [ metricRow( - zh ? "下行总量" : "Total downlink", + message("total_downlink"), off.fixture_tx_bytes_all.sum, on.fixture_tx_bytes_all.sum, (v) => fmt(v) + " B", ), metricRow( - zh ? "下行 P50" : "Downlink P50", + message("downlink_p50"), off.fixture_tx_bytes_all.p50, on.fixture_tx_bytes_all.p50, (v) => fmt(v) + " B", ), metricRow( - zh ? "上行总量" : "Total uplink", + message("total_uplink"), rx.off.total_bytes, rx.on.total_bytes, (v) => fmt(v) + " B", ), metricRow( - zh ? "上行 P50" : "Uplink P50", + message("uplink_p50"), rx.off.p50_bytes, rx.on.p50_bytes, (v) => fmt(v) + " B", @@ -666,27 +452,25 @@ function resourceGroups() { ); let startup = table( [ - zh - ? `冷启动指标 · 关闭 ${M.cold_start.off.n} / 开启 ${M.cold_start.on.n} 次` - : `Cold-start metric · off ${M.cold_start.off.n} / on ${M.cold_start.on.n}`, + message("cold_start_metric_off_on", {value0: (M.cold_start.off.n), value1: (M.cold_start.on.n)}), T.modes.off, T.modes.on, ], [ metricRow( - zh ? "就绪时间 P50" : "Ready time P50", + message("ready_time_p50"), cold.off.ready_ms.p50, cold.on.ready_ms.p50, ms, ), metricRow( - zh ? "启动 CPU P50" : "Launch CPU P50", + message("launch_cpu_p50"), cold.off.launch_cpu_ms.p50, cold.on.launch_cpu_ms.p50, ms, ), metricRow( - zh ? "启动观测 PSS P50" : "Launch observed PSS P50", + message("launch_observed_pss_p50"), cold.off.launch_pss_bytes.p50, cold.on.launch_pss_bytes.p50, mib, @@ -695,12 +479,12 @@ function resourceGroups() { ); let leaderOf = (a, b) => { let winner = best(a, b, "min"); - return winner ? T.modes[winner] : zh ? "持平" : "tie"; + return winner ? T.modes[winner] : message("tie_f4719e"); }; - let leader = `
${esc(zh ? "采样执行中的资源数值" : "Observed profiled resource values")} · ${esc(zh ? `全部调用 CPU:${leaderOf(off.cpu_ms_all.sum, on.cpu_ms_all.sum)};观测 PSS 最大值 P50:${leaderOf(off.pss_peak_mib_all.p50, on.pss_peak_mib_all.p50)};站点下行:${leaderOf(off.fixture_tx_bytes_all.sum, on.fixture_tx_bytes_all.sum)}。这些是采样执行的观测值;主耗时采用无采样轮。` : `All-call CPU: ${leaderOf(off.cpu_ms_all.sum, on.cpu_ms_all.sum)}; observed PSS maximum P50: ${leaderOf(off.pss_peak_mib_all.p50, on.pss_peak_mib_all.p50)}; fixture downlink: ${leaderOf(off.fixture_tx_bytes_all.sum, on.fixture_tx_bytes_all.sum)}. These are profiled observations; primary duration uses unprofiled calls.`)}
`; + let leader = `
${esc(message("observed_profiled_resource_values"))} · ${esc(message("all_call_cpu_observed_pss_maximum_p50_fixture_downlink_these_are_", {value0: (leaderOf(off.cpu_ms_all.sum, on.cpu_ms_all.sum)), value1: (leaderOf(off.pss_peak_mib_all.p50, on.pss_peak_mib_all.p50)), value2: (leaderOf(off.fixture_tx_bytes_all.sum, on.fixture_tx_bytes_all.sum))}))}
`; return ( leader + - `

${esc(zh ? "CPU 与计算" : "CPU and computation")}

${cpu}

${esc(zh ? "内存" : "Memory")}

${memory}

${esc(zh ? "网络流量" : "Network traffic")}

${network}

${esc(zh ? "冷启动" : "Cold start")}

${startup}
` + `

${esc(message("cpu_and_computation"))}

${cpu}

${esc(message("memory"))}

${memory}

${esc(message("network_traffic"))}

${network}

${esc(message("cold_start"))}

${startup}
` ); } function pairStatus(c) { @@ -714,6 +498,13 @@ function statusChip(s) { function simpleCard(label, value, detail) { return card(label, `${value}`, esc(detail)); } +function inputVersionNote() { + const count = Object.keys(M.input_definition_variants || {}).length; + if (!count) return ""; + const shared = M.task_count - count; + return `

${esc(message("tasks_used_different_definition_versions_across_configurations_th", {value0: (count), value1: (shared)}))}

`; +} + function home() { return ( hero( @@ -722,12 +513,13 @@ function home() { `
${esc(T.nav.analysis)}${esc(T.method)}`, ) + functionalMethodSummary() + + inputVersionNote() + combinedSummaryChart() + - `
${esc(zh ? "查看完整资源统计与测量范围" : "Inspect resource statistics and measurement scope")}${candidateResourceSection()}
` + + `
${esc(message("inspect_resource_statistics_and_measurement_scope"))}${candidateResourceSection()}
` + section( - zh ? "如何查看结果" : "Explore the results", + message("explore_the_results"), "", - `

${esc(resourceBuildNote())}

${esc(zh ? "比较新增通过与仍然失败的用例" : "Compare gained passes and remaining failures")} · ${esc(T.nav.guide)} · ${esc(T.nav.reproduce)}`, + `

${esc(resourceBuildNote())}

${esc(message("compare_gained_passes_and_remaining_failures"))} · ${esc(T.nav.guide)} · ${esc(T.nav.reproduce)}`, ) ); } @@ -736,14 +528,14 @@ function successfulDuration() { b = M.metrics.on.baseline; return table( [ - zh ? "成功调用耗时" : "Accepted-call duration", + message("accepted_call_duration"), `${T.modes.off} · n=${a.passes}`, `${T.modes.on} · n=${b.passes}`, ], [ - ["mean", zh ? "成功调用平均耗时" : "Mean accepted-call duration"], - ["p50", zh ? "成功调用 P50 耗时" : "Accepted-call P50 duration"], - ["p95", zh ? "成功调用 P95 耗时" : "Accepted-call P95 duration"], + ["mean", message("mean_accepted_call_duration")], + ["p50", message("accepted_call_p50_duration")], + ["p95", message("accepted_call_p95_duration")], ].map(([key, label]) => metricRow(label, a.duration_ms_pass[key], b.duration_ms_pass[key], ms), ), @@ -755,14 +547,14 @@ function strataTable() { off = a.filter((c) => c.off_passes === M.protocol.k_runs).length, on = a.filter((c) => c.on_passes === M.protocol.k_runs).length, total = a.length; - return `${esc(T.strata[s] || s)}
${a.length} ${zh ? "题" : "tasks"}
${off}/${total}${on}/${total}
`; + return `${esc(T.strata[s] || s)}
${a.length} ${message("tasks")}
${off}/${total}${on}/${total}
`; }); return table( [ - zh ? "任务类别" : "Family", + message("family"), T.modes.off, T.modes.on, - zh ? "开布局通过占比" : "Layout-on pass share", + message("layout_on_pass_share"), ], rows, ); @@ -784,15 +576,15 @@ function inventory(kind) { return ( hero(title, intro) + section( - zh ? "查找与筛选" : "Find and filter", + message("find_and_filter"), "", filters + `
`, ) + (kind === "dataset" ? section( - zh ? "选择规则" : "Selection rule", + message("selection_rule"), T.methodLimit, - `

${esc(zh ? "选取 1,928 题中未能三次全部通过的全部 372 题。保留 Chrome 未通过的题目;Chrome 的结果不作为排除依据。两个布局组使用同一组冻结任务与验收规则。" : M.selection)}

SHA-256: ${esc(M.manifest_sha256)}

`, + `

${esc(M.selection)}

SHA-256: ${esc(M.manifest_sha256)}

`, ) : "") ); @@ -841,7 +633,7 @@ function renderInventory() { if (dataset) { document.getElementById("relation").closest("label").hidden = true; document.getElementById("inventory-table").innerHTML = table( - [T.task, T.stratum, zh ? "子集" : "Subset", T.rawContract], + [T.task, T.stratum, message("subset"), T.rawContract], rows.map( (c) => `${esc(c.title)}
${esc(c.id)}
${esc(T.strata[c.stratum] || c.stratum)}${esc(c.subset)}${esc(T.open)}`, @@ -901,15 +693,15 @@ function pairedAttempts(c) { }); return table( [ - zh ? "序号" : "Attempt", - zh ? "无布局状态" : "Off status", - zh ? "开布局状态" : "On status", - zh ? "无布局耗时" : "Off duration", - zh ? "开布局耗时" : "On duration", - zh ? "无布局 CPU" : "Off CPU", - zh ? "开布局 CPU" : "On CPU", - zh ? "无布局结束 PSS" : "Off ending PSS", - zh ? "开布局结束 PSS" : "On ending PSS", + message("attempt"), + message("off_status"), + message("on_status"), + message("off_duration"), + message("on_duration"), + message("off_cpu"), + message("on_cpu"), + message("off_ending_pss"), + message("on_ending_pss"), ], rows, "paired-matrix", @@ -918,20 +710,20 @@ function pairedAttempts(c) { async function taskPage(id, contract = false) { let c = await getCase(id); let f = facts([ - [zh ? "任务 ID" : "Task ID", `${esc(c.id)}`], - [zh ? "子集" : "Subset", esc(c.subset)], + [message("task_id"), `${esc(c.id)}`], + [message("subset"), esc(c.subset)], [ - zh ? "任务定义 SHA-256" : "Task SHA-256", + message("task_sha_256"), `${esc(c.task_sha256)}`, ], [ - zh ? "协议特性" : "Features", + message("features"), c.features.map((x) => `${esc(x)}`).join(" ") || "—", ], ]); let results = table( - [T.mode, zh ? "用例判定" : "Case verdict", T.open], + [T.mode, message("case_verdict"), T.open], ["off", "on"].map( (m) => `${esc(T.modes[m])}${caseVerdict(c.modes[m].passes)}${esc(T.more)} →`, @@ -943,27 +735,25 @@ async function taskPage(id, contract = false) { (contract ? "" : section( - zh ? "同题结果" : "Same-task results", + message("same_task_results"), casePopulation(), results, ) + section( - zh ? "逐次配对" : "Paired attempts", - zh - ? "耗时来自无采样轮;CPU 和结束 PSS 来自同题同次的资源采样轮。点击状态可直达对应调用的证据。" - : "Duration comes from the unprofiled run; CPU and ending PSS come from the matching profiled call. Open a status to inspect that exact call's evidence.", + message("paired_attempts"), + message("duration_comes_from_the_unprofiled_run_cpu_and_ending_pss_come_fr"), pairedAttempts(c), )) + (contract ? section( T.rawContract, "", - `
${f}

${esc(zh ? "驱动步骤" : "Driver steps")}

${esc(JSON.stringify(c.driver, null, 2))}
`, + `
${f}

${esc(message("driver_steps"))}

${esc(JSON.stringify(c.driver, null, 2))}
`, ) + section( - zh ? "验收合同" : "Acceptance contract", + message("acceptance_contract"), "", - `

${esc(zh ? "场景" : "Scene")}

${esc(JSON.stringify(c.scene, null, 2))}

${esc(zh ? "评分规则" : "Grader")}

${esc(JSON.stringify(c.grader, null, 2))}
`, + `

${esc(message("scene"))}

${esc(JSON.stringify(c.scene, null, 2))}

${esc(message("grader"))}

${esc(JSON.stringify(c.grader, null, 2))}
`, ) : section( T.rawContract, @@ -978,12 +768,8 @@ function evidenceButtons(a, mode, id) { let files = a.evidence[phase] || {}, phaseName = phase === "baseline" - ? zh - ? "无采样" - : "Unprofiled" - : zh - ? "资源采样" - : "Profiled"; + ? message("unprofiled") + : message("profiled"); for (let [name, info] of Object.entries(files)) groups.push( ``, @@ -994,23 +780,23 @@ function evidenceButtons(a, mode, id) { function attemptCard(a, mode, id) { let r = a.resource || {}, err = a.failure?.detail || "", - details = `

${esc(zh ? "评分与响应" : "Verdict and response")}

${facts( + details = `

${esc(message("verdict_and_response"))}

${facts( [ [T.status, statusChip(a.status)], - [zh ? "采样轮状态" : "Profiled verdict", statusChip(a.profile_status)], + [message("profiled_verdict"), statusChip(a.profile_status)], [T.duration, esc(ms(a.duration_ms))], - [zh ? "响应" : "Answer", `${esc(a.answer || "—")}`], + [message("answer"), `${esc(a.answer || "—")}`], [T.failure, esc(a.failure?.class || "—")], ], - )}

${esc(zh ? "Moli 资源" : "Moli resources")}

${facts([ + )}

${esc(message("moli_resources"))}

${facts([ [T.cpu, esc(ms(r.cpu_total_ms))], [T.pss, esc(mib(r.pss_end_bytes))], [ - zh ? "观测 PSS 最大值" : "Observed PSS maximum", + message("observed_pss_maximum"), esc(mib(r.pss_peak_bytes)), ], [T.traffic, esc(fmt(r.fixture_tx_bytes) + " B")], - [zh ? "采样点" : "Samples", esc(fmt(r.samples_seen))], + [message("samples"), esc(fmt(r.samples_seen))], ])}
${err ? `
${esc(T.failure)}: ${esc(err)}
` : ""}`; return `
${esc(T.attempt.replace("{n}", a.ordinal))}${statusChip(a.status)}${esc(ms(a.duration_ms))} · ${esc(ms(r.cpu_total_ms))} CPU
${esc(T.details)}${details}
`; } @@ -1020,39 +806,35 @@ async function resultPage(id, mode) { x = c.modes[mode], y = c.modes[mode === "off" ? "on" : "off"]; let firstFail = x.attempts.find((a) => a.status !== "pass"); - let summary = `
${card(zh ? "用例判定" : "Case verdict", caseVerdict(x.passes), zh ? `${x.passes}/${M.protocol.k_runs} 次执行通过;全部通过才计通过用例` : `${x.passes}/${M.protocol.k_runs} attempts passed; all must pass`)}${card(T.duration, ms(x.attempts.reduce((n, a) => n + a.duration_ms, 0)), zh ? `${M.protocol.k_runs} 次总耗时` : `Total for ${M.protocol.k_runs} calls`)}${card(T.relation, esc(T[pairStatus({ off_passes: c.modes.off.passes, on_passes: c.modes.on.passes })]), zh ? "与另一模式比较" : "Compared with the other mode")}
`; + let summary = `
${card(message("case_verdict"), caseVerdict(x.passes), message("attempts_passed_all_must_pass", {value0: (x.passes), value1: (M.protocol.k_runs)}))}${card(T.duration, ms(x.attempts.reduce((n, a) => n + a.duration_ms, 0)), message("total_for_calls", {value0: (M.protocol.k_runs)}))}${card(T.relation, esc(T[pairStatus({ off_passes: c.modes.off.passes, on_passes: c.modes.on.passes })]), message("compared_with_the_other_mode"))}
`; let locator = firstFail - ? `
${esc(zh ? "失败定位" : "Failure locator")} · ${esc(T.attempt.replace("{n}", firstFail.ordinal))} · ${esc(firstFail.failure.class || T.unavailable)}
${esc(firstFail.failure.detail || T.unavailable)}
` - : `
${esc(zh ? `本模式 ${M.protocol.k_runs} 次调用均通过。` : `All ${M.protocol.k_runs} calls passed in this mode.`)}
`; + ? `
${esc(message("failure_locator"))} · ${esc(T.attempt.replace("{n}", firstFail.ordinal))} · ${esc(firstFail.failure.class || T.unavailable)}
${esc(firstFail.failure.detail || T.unavailable)}
` + : `
${esc(message("all_calls_passed_in_this_mode", {value0: (M.protocol.k_runs)}))}
`; return ( `` + hero(c.title, `${T.modes[mode]} · ${c.subset}`) + - section(zh ? "结果摘要" : "Result summary", "", summary + locator) + + section(message("result_summary"), "", summary + locator) + section( - zh ? "运行标识" : "Run identities", + message("run_identities"), "", `
${facts([ [ - zh ? "无采样轮" : "Unprofiled run", + message("unprofiled_run"), `${esc(M.runs[mode + "_baseline"].run_id)}`, ], [ - zh ? "资源采样轮" : "Profiled run", + message("profiled_run"), `${esc(M.runs[mode + "_engine"].run_id)}`, ], ])}
`, ) + section( - zh - ? `${M.protocol.k_runs} 次调用与证据` - : `${M.protocol.k_runs} calls and evidence`, - zh - ? "每次调用保留无采样的结果与耗时,以及同题同次资源采样值。点击证据可查看原始评分、输出、日志或 CDP 消息。" - : "Each call pairs the unprofiled verdict and duration with the profiled resource observation. Open evidence for exact grader, output, log or CDP data.", + message("calls_and_evidence", {value0: (M.protocol.k_runs)}), + message("each_call_pairs_the_unprofiled_verdict_and_duration_with_the_prof"), `
${x.attempts.map((a) => attemptCard(a, mode, id)).join("")}
`, ) + section( - zh ? "另一模式" : "Other mode", + message("other_mode"), "", ``, ) @@ -1062,95 +844,69 @@ function methodPage() { return ( hero( T.nav.method, - zh - ? "固定任务、固定布局与独立资源实验。" - : "Fixed tasks, fixed layout and a separate resource experiment.", + message("fixed_tasks_fixed_layout_and_a_separate_resource_experiment"), ) + section( - zh ? "评测流程" : "Evaluation protocol", + message("evaluation_protocol"), "", `
flowchart TB
-A["${zh ? "冻结任务、二进制与验收规则" : "Freeze tasks, binaries and acceptance rules"}"] --> B["${zh ? "固定版本与 layout 配置" : "Fix version and layout configuration"}"]
-B --> C["${zh ? "功能:372 题,每题 3 次" : "Functional: 372 cases, three attempts each"}"]
-B --> D["${zh ? "资源:557 题,每题 5 次" : "Resources: 557 tasks, five calls each"}"]
-C --> E["${zh ? "三次全部通过才算通过" : "A case passes only when all three attempts pass"}"]
-D --> F["${zh ? "全部调用统计与固定 209 题图表样本" : "All-call statistics and fixed 209-task chart sample"}"]
-E --> G["${zh ? "配置总表与总图" : "Configuration table and chart"}"]
+A["${message("freeze_tasks_binaries_and_acceptance_rules")}"] --> B["${message("fix_version_and_layout_configuration")}"]
+B --> C["${message("functional_372_cases_three_attempts_each")}"]
+B --> D["${message("resources_557_tasks_five_calls_each")}"]
+C --> E["${message("a_case_passes_only_when_all_three_attempts_pass")}"]
+D --> F["${message("all_call_statistics_and_fixed_209_task_chart_sample")}"]
+E --> G["${message("configuration_table_and_chart")}"]
 F --> G
`, ) + section( - zh ? "功能评分与基线" : "Functional scoring and baseline", + message("functional_scoring_and_baseline"), "", - `

${esc(zh ? "所有配置共用 372 题分母。每题三次执行全部通过运行与验收检查,才记为一个通过用例。1.1.8 的两种布局、修复版 1.1.9 的两种布局和 1.1.9 的两种布局均覆盖全部 372 题。" : "All configurations use the same 372-case denominator. A case passes only when execution and acceptance checks pass in all three attempts. Both 1.1.8 layouts, both patched 1.1.9 layouts and both 1.1.9 layouts directly cover all 372 cases.")}

${esc(zh ? "1.1.4 关闭布局为基线:对 1.1.8 关闭布局唯一通过的候选题实测三次,均失败;其余 371 题沿用共同失败关系。因此表中的 0/372 包含 1 题直接验证和 371 题继承结果,并非对 1.1.4 的完整重测。基线通过率为 0%,通过率变化用百分点表示。" : "Layout-off 1.1.4 is the baseline: the sole candidate passed by layout-off 1.1.8 failed all three direct checks; the other 371 retain the shared-failure relation. Its 0/372 consists of one directly checked case and 371 inherited outcomes, not a complete 1.1.4 rerun. Because baseline success is zero, success changes are expressed in percentage points.")}

`, + `

${esc(message("all_configurations_use_the_same_372_case_denominator_a_case_passe"))}

${esc(message("layout_off_1_1_4_is_the_baseline_the_sole_candidate_passed_by_lay"))}

`, ) + section( - zh ? "任务来源与适用范围" : "Task selection and scope", + message("task_selection_and_scope"), "", - `

${esc(zh ? "372 题选自 Moli 0.1.1 在 1,928 题中未达到三次全部通过的全部用例,覆盖 15 类驱动。Chrome 结果不影响入选。其余 1,556 题不在评测范围内,这里的通过率不代表完整 1,928 题的通过率。" : "The 372 cases are all cases from the 1,928-case suite that Moli 0.1.1 did not pass in all three attempts, spanning 15 driver types. Chrome outcomes do not affect selection. The other 1,556 cases are outside this evaluation, so these rates do not describe the full suite.")}

${esc(T.nav.dataset)}`, + `

${esc(message("the_372_cases_are_all_cases_from_the_1_928_case_suite_that_moli_0"))}

${esc(T.nav.dataset)}`, ) + (M.linux_resource_profiles ? unifiedResourceMethodology() : section( - zh ? "资源测量与可比范围" : "Resource measurement and comparability", + message("resource_measurement_and_comparability"), "", - `

${esc(zh ? "资源实验单独执行 557 题,每题五次,每种配置共 2,785 次调用。图和总表使用运行前固定的 209 题、1,045 次样本;完整资源表另列全部 557 题。P50 是中位数,P95 是第 95 百分位。" : "Resources use a separate 557-task experiment with five calls per task, or 2,785 calls per configuration. The chart and overview table use 1,045 calls on 209 tasks fixed before execution; detailed resource tables also show all 557 tasks. P50 is the median and P95 the 95th percentile.")}

${esc(zh ? "1.1.4 / 1.1.8 在 Mac 发起的 Docker Linux 容器中执行,使用 Linux ARM64 二进制并采集 PSS;1.1.9 在 macOS 原生执行,使用 Mach-O ARM64 二进制并采集 RSS。七组均为本次系列的复测数据,非官网 71 MiB 数据;同一宿主机不等于相同执行环境。CPU 为进程树用户态与内核态时间之和。跨平台的内存和 CPU 不计算相对 1.1.4 的百分比变化;同平台百分比是样本数值对照。四组 macOS 资源配置均为 release 构建,使用同一冻结调用矩阵,逐次轮换顺序并串行执行。" : "Moli 1.1.4 / 1.1.8 run in Docker Linux containers launched from the Mac, using Linux ARM64 binaries and PSS sampling. Moli 1.1.9 runs natively on macOS with Mach-O ARM64 binaries and RSS sampling. All seven configurations are reruns in this series, not the website’s 71 MiB result. Sharing a host does not imply an identical execution environment. CPU is process-tree user plus system time. Cross-platform memory and CPU changes relative to 1.1.4 are not calculated; same-platform percentages describe the measured sample. All four macOS resource configurations use release builds and one frozen call matrix, executed serially with rotating order.")}

`, + `

${esc(message("resources_use_a_separate_557_task_experiment_with_five_calls_per_"))}

${esc(message("moli_1_1_4_1_1_8_run_in_docker_linux_containers_launched_from_the"))}

`, )) + section( - zh ? "RSS 与 PSS:指标口径" : "RSS and PSS: memory definitions", - zh - ? "两者均以 MiB 展示,但共享内存的计数方式不同,不能互换或按固定比例换算。" - : "Both use MiB, but account for shared memory differently and cannot be interchanged or converted by a fixed ratio.", + message("rss_and_pss_memory_definitions"), + message("both_use_mib_but_account_for_shared_memory_differently_and_cannot"), table( - [zh ? "对比项" : "Property", "RSS", "PSS"], + [message("property"), "RSS", "PSS"], [ [ - zh ? "统计内容" : "What is counted", - zh - ? "进程当前驻留物理内存中的页" - : "Pages currently resident in physical memory for the process", - zh - ? "私有驻留页,加上按共享进程数分摊的共享驻留页" - : "Private resident pages plus a proportional share of shared resident pages", + message("what_is_counted"), + message("pages_currently_resident_in_physical_memory_for_the_process"), + message("private_resident_pages_plus_a_proportional_share_of_shared_reside"), ], [ - zh ? "共享页" : "Shared pages", - zh - ? "每个映射进程都计入;进程树求和会重复计入共享页" - : "Counted for each mapping process; process-tree sums can count shared pages multiple times", - zh - ? "按共享份额计入,减少进程间重复计算" - : "Counted proportionally, reducing double counting across processes", + message("shared_pages"), + message("counted_for_each_mapping_process_process_tree_sums_can_count_shar"), + message("counted_proportionally_reducing_double_counting_across_processes"), ], [ - zh ? "示例" : "Example", - zh - ? "20 MiB 私有页+与另一进程共享的 10 MiB:RSS=30 MiB" - : "20 MiB private plus 10 MiB shared equally with one other process: RSS = 30 MiB", - zh - ? "同例:PSS=20+10÷2=25 MiB" - : "Same example: PSS = 20 + 10 / 2 = 25 MiB", + message("example"), + message("20_mib_private_plus_10_mib_shared_equally_with_one_other_process_"), + message("same_example_pss_20_10_2_25_mib"), ], [ - zh ? "本报告采集" : "Collection in this report", + message("collection_in_this_report"), M.linux_resource_profiles - ? zh - ? "本报告资源对比统一使用 PSS" - : "This report consistently compares PSS" - : zh - ? "macOS:ps 的 rss 字段,按进程树求和" - : "macOS: ps rss field, summed over the process tree", - zh - ? "Linux:/proc/PID/smaps_rollup 的 Pss 字段,按进程树求和" - : "Linux: Pss in /proc/PID/smaps_rollup, summed over the process tree", + ? message("this_report_consistently_compares_pss") + : message("macos_ps_rss_field_summed_over_the_process_tree"), + message("linux_pss_in_proc_pid_smaps_rollup_summed_over_the_process_tree"), ], [ - zh ? "比较条件" : "Comparison conditions", - zh - ? "相同执行环境、采样与任务范围;CPU 时间也须采用一致条件" - : "Matching execution environment, sampling and task scope; CPU comparisons also require consistent conditions", - zh - ? "相同条件,且保持共享进程环境一致;PSS 不等于 RSS" - : "Matching conditions and shared-process environment; PSS is not RSS", + message("comparison_conditions"), + message("matching_execution_environment_sampling_and_task_scope_cpu_compar"), + message("matching_conditions_and_shared_process_environment_pss_is_not_rss"), ], ].map( (cells) => @@ -1158,12 +914,12 @@ F --> G
`, ), "memory-definition-table", ) + - `

${esc(zh ? "Linux 内核:RSS 与 PSS 定义" : "Linux kernel: RSS and PSS definitions")} · ${esc(zh ? "Apple:驻留内存与共享映射" : "Apple: resident memory and shared mappings")}

`, + `

${esc(message("linux_kernel_rss_and_pss_definitions"))} · ${esc(message("apple_resident_memory_and_shared_mappings"))}

`, ) + section( - zh ? "执行与证据" : "Execution and evidence", + message("execution_and_evidence"), "", - `

${esc(zh ? "资源实验全局同时只执行一次任务调用。功能与资源分别绑定构建身份;成功率适用于对应的功能构建。任务定义、程序判定、标准输出与错误日志可从逐题详情检查。未进行独立人工逐次复核。" : "The resource experiment executes one task call globally at a time. Functional and resource measurements bind their build identities separately. Success applies to the corresponding functional build. Task definitions, program verdicts, stdout and error logs are available in task details. No independent per-call human review was performed.")}

${esc(T.nav.reproduce)}`, + `

${esc(message("the_resource_experiment_executes_one_task_call_globally_at_a_time"))}

${esc(T.nav.reproduce)}`, ) ); } @@ -1182,19 +938,15 @@ function costSection() { .reduce((n, c) => n + c.off_duration_ms - c.on_duration_ms, 0), share = total === 0 ? "—" : fmt((top / total) * 100, 1); return section( - zh ? "耗时下降来自哪些任务" : "Where the time reduction came from", - zh - ? `以下三题贡献净耗时变化的 ${share}%。正值表示开启布局后耗时下降;负值表示增加。CPU 差值来自开启资源采集的调用,耗时差值来自关闭资源采集的调用。` - : `These three tasks account for ${share}% of the net duration change. Positive differences mean lower duration with layout; negative values mean higher duration. CPU differences use profiled calls and duration differences use unprofiled calls.`, + message("where_the_time_reduction_came_from"), + message("these_three_tasks_account_for_of_the_net_duration_change_positive", {value0: (share)}), table( [ T.task, - zh ? "用例判定" : "Case verdict", - zh - ? `${M.protocol.k_runs} 次总耗时` - : `${M.protocol.k_runs}-call duration`, - zh ? "耗时减少" : "Time reduction", - zh ? "CPU 减少" : "CPU reduction", + message("case_verdict"), + message("call_duration", {value0: (M.protocol.k_runs)}), + message("time_reduction"), + message("cpu_reduction"), ], rows, "cost-contributors", @@ -1207,19 +959,17 @@ function fixedRelations() { [ "119_off", "119_on", - zh - ? "同一修复版:开启 layout 的变化" - : "Same patched build: enabling layout", + message("same_patched_build_enabling_layout"), ], [ "119_official_on", "119_on", - zh ? "同为开启 layout:修复版与 1.1.9" : "Layout on: patched vs. 1.1.9", + message("layout_on_patched_vs_1_1_9"), ], [ "119_official_off", "119_official_on", - zh ? "1.1.9:开启 layout 的变化" : "1.1.9: enabling layout", + message("1_1_9_enabling_layout"), ], ]; return pairs @@ -1242,21 +992,19 @@ function fixedComparisonSection() { `${esc(p.label)}${p.counts.gain}${p.counts.loss}${p.counts.bothPass}${p.counts.bothFail}`, ); return section( - zh ? "布局变化与版本变化" : "Layout changes and version changes", - zh - ? "同一修复版的开关对照用于判断布局变化;固定开启 layout 的 1.1.9 与修复版对照用于定位版本间变化。下表保留新增通过和退步,不能只看净增加。" - : "Compare layout off/on within the patched build to measure layout changes. Compare 1.1.9 and patched 1.1.9 with layout on to locate version differences. Gains and losses are both retained.", + message("layout_changes_and_version_changes"), + message("compare_layout_off_on_within_the_patched_build_to_measure_layout_"), table( [ - zh ? "对照" : "Comparison", - zh ? "新增通过" : "Gained", - zh ? "退步" : "Lost", - zh ? "均通过" : "Both pass", - zh ? "均失败" : "Both fail", + message("comparison"), + message("gained"), + message("lost"), + message("both_pass"), + message("both_fail"), ], rows, ) + - `
`, + `
`, ); } function wireFixedExplorer() { @@ -1299,9 +1047,9 @@ async function fixedCasePage(id) { methods = M.functional_methods.filter((x) => c.methods[x.id]); let summary = table( [ - zh ? "方法" : "Method", - zh ? "通过次数" : "Passed attempts", - zh ? "用例判定" : "Case verdict", + message("method"), + message("passed_attempts"), + message("case_verdict"), ], methods.map( (m) => @@ -1330,17 +1078,17 @@ async function fixedCasePage(id) { return ( `` + hero(c.title, id) + - section(zh ? "结果摘要" : "Result summary", "", summary) + + section(message("result_summary"), "", summary) + section( - zh ? "任务要求" : "Task requirements for this comparison", + message("task_requirements_for_this_comparison"), c.task_definition.description, - `
${esc(zh ? "查看冻结任务定义" : "Inspect frozen task definition")}

SHA-256: ${esc(c.task_sha256)}

${esc(JSON.stringify(c.task_definition, null, 2))}
`, + `
${esc(message("inspect_frozen_task_definition"))}

SHA-256: ${esc(c.task_sha256)}

${esc(JSON.stringify(c.task_definition, null, 2))}
`, ) + details + section( - zh ? "同题版本对照" : "Same-task version comparison", + message("same_task_version_comparison"), "", - `${esc(zh ? "查看 1.1.8 任务要求与结果" : "View 1.1.8 task requirements and results")}`, + `${esc(message("view_1_1_8_task_requirements_and_results"))}`, ) ); } @@ -1360,7 +1108,7 @@ function analysisPage() { hero(T.nav.analysis, T.analysisIntro) + fixedComparisonSection() + section( - zh ? "1.1.8 用例通过关系" : "Moli 1.1.8 case outcomes", + message("moli_1_1_8_case_outcomes"), casePopulation(), `
${[ ["gain", p.off_fail_on_pass], @@ -1371,15 +1119,15 @@ function analysisPage() { .map(([key, n]) => card(T[key], `${n} / ${M.task_count}`, "")) .join("")}
`, ) + - section(zh ? "新增通过的用例" : "Cases gained", "", changed(gains)) + - section(zh ? "退步的用例" : "Cases lost", "", changed(losses)) + + section(message("cases_gained"), "", changed(gains)) + + section(message("cases_lost"), "", changed(losses)) + section( - zh ? "失败证据" : "Failure evidence", + message("failure_evidence"), "", - ``, + ``, ) + costSection() + - section(zh ? "任务类型比较" : "By task family", "", strataTable()) + section(message("by_task_family"), "", strataTable()) ); } function candidateResourceQuality() { @@ -1388,20 +1136,18 @@ function candidateResourceQuality() { .filter((m) => M.linux_resource_profiles.configurations[m.id]) .map((method) => { const config = M.linux_resource_profiles.configurations[method.id]; - return `${esc(zh ? method.label : method.label_en)}${fmt(config.metrics.all_predeclared_calls.pss_peak_mib.n)}/2,785${fmt(config.metrics.all_predeclared_calls.cpu_time_ms.n)}/2,785${esc(config.calibration.acceptable ? (zh ? "通过" : "Pass") : zh ? "失败" : "Fail")}${esc(Object.values(config.host).every((h) => !h.polluted) ? (zh ? "通过" : "Pass") : zh ? "失败" : "Fail")}`; + return `${esc(zh ? method.label : method.label_en)}${fmt(config.metrics.all_predeclared_calls.pss_peak_mib.n)}/2,785${fmt(config.metrics.all_predeclared_calls.cpu_time_ms.n)}/2,785${esc(config.calibration.acceptable ? (message("pass")) : message("fail"))}${esc(Object.values(config.host).every((h) => !h.polluted) ? (message("pass")) : message("fail"))}`; }); return section( - zh ? "1.1.9 资源测量质量" : "1.1.9 resource measurement quality", - zh - ? "按官方流程执行 profiler-off 校准和 profiler-on 测量;核验完整题目范围、构建身份、PSS/CPU 覆盖、采集器影响和宿主机负载。" - : "The official procedure pairs profiler-off calibration with profiler-on measurement and checks task coverage, build identity, PSS/CPU availability, observer effect and host load.", + message("1_1_9_resource_measurement_quality"), + message("the_official_procedure_pairs_profiler_off_calibration_with_profil"), table( [ - zh ? "配置" : "Configuration", - zh ? "PSS 覆盖" : "PSS coverage", - zh ? "CPU 覆盖" : "CPU coverage", - zh ? "采集器校准" : "Profiler calibration", - zh ? "宿主机检查" : "Host check", + message("configuration"), + message("pss_coverage"), + message("cpu_coverage"), + message("profiler_calibration"), + message("host_check"), ], rows, ), @@ -1412,19 +1158,17 @@ function candidateResourceQuality() { let rows = methods.map((item) => { let m = item.config.metrics.all_predeclared_calls, h = item.config.quality; - return `${esc(item.label)}${fmt(m.rss_peak_mib.n)}/2,785${fmt(m.cpu_time_ms.n)}/2,785${esc(h.profiled_host.polluted ? (zh ? "未通过" : "Failed") : zh ? "通过" : "Pass")}`; + return `${esc(item.label)}${fmt(m.rss_peak_mib.n)}/2,785${fmt(m.cpu_time_ms.n)}/2,785${esc(h.profiled_host.polluted ? (message("failed")) : message("pass"))}`; }); return section( - zh ? "Moli 1.1.9 资源证据质量" : "Moli 1.1.9 resource evidence quality", - zh - ? "每个固定配置测一轮,核验完整任务矩阵、二进制身份、资源覆盖与宿主机观测。未单独测量采样器带来的耗时影响。" - : "Each fixed configuration has one measured run. Checks cover the complete task matrix, binary identity, resource coverage, and host observations. Profiler overhead was not separately measured.", + message("moli_1_1_9_resource_evidence_quality"), + message("each_fixed_configuration_has_one_measured_run_checks_cover_the_co"), table( [ - zh ? "配置" : "Configuration", - zh ? "RSS 覆盖" : "RSS coverage", - zh ? "CPU 覆盖" : "CPU coverage", - zh ? "宿主机检查" : "Host check", + message("configuration"), + message("rss_coverage"), + message("cpu_coverage"), + message("host_check"), ], rows, ), @@ -1438,19 +1182,17 @@ function reviewPage() { return ( hero( T.nav.review, - zh - ? "核对评测覆盖、二进制身份与证据来源。" - : "Verify evaluation coverage, binary identities and evidence sources.", + message("verify_evaluation_coverage_binary_identities_and_evidence_sources"), ) + section( - zh ? "功能证据覆盖" : "Functional evidence coverage", + message("functional_evidence_coverage"), "", table( [ - zh ? "方法" : "Method", - zh ? "直接验证题数" : "Direct cases", - zh ? "继承结果题数" : "Inherited cases", - zh ? "调用数" : "Calls", + message("method"), + message("direct_cases_5505c5"), + message("inherited_cases"), + message("calls"), "SHA-256", ], rows, @@ -1458,9 +1200,9 @@ function reviewPage() { ) + candidateResourceQuality() + section( - zh ? "评分与复核范围" : "Scoring and review coverage", + message("scoring_and_review_coverage"), "", - `

${esc(zh ? "结果来自冻结验收规则的程序判定。报告检查调用矩阵、版本和任务哈希、资源样本数、聚合数值及证据文件完整性。未进行独立人工逐次复核;程序通过结果不等同于人工审查结论。" : "Outcomes come from programmatic evaluation against frozen acceptance rules. Report checks cover call matrices, binary and task hashes, resource sample counts, aggregates and evidence integrity. No independent per-call human review was performed; program passes are not human-review conclusions.")}

${esc(zh ? "下载完整数据与证据索引" : "Download the data model and evidence index")}`, + `

${esc(message("outcomes_come_from_programmatic_evaluation_against_frozen_accepta"))}

${esc(message("download_the_data_model_and_evidence_index"))}`, ) ); } @@ -1476,25 +1218,26 @@ function guidePage() { }; const relationText = relations.map((r) => { const net = r.counts.gain - r.counts.loss; - return `

${esc(methodLabel(r.a))} → ${esc(methodLabel(r.b))}:${esc(zh ? `新增通过 ${r.counts.gain} 题,退步 ${r.counts.loss} 题,通过数净变化 ${signed(net, 0, " 题")}。` : `${r.counts.gain} cases gained, ${r.counts.loss} lost; net change ${signed(net, 0, " cases")}.`)}

`; + return `

${esc(methodLabel(r.a))} → ${esc(methodLabel(r.b))}:${esc(message("outcome_change_summary", {value0: r.counts.gain, value1: r.counts.loss, value2: signed(net, 0, "")}))}

`; }).join(""); return ( hero(T.nav.guide, reportSubtitle()) + functionalMethodSummary() + + inputVersionNote() + combinedSummaryChart() + (relations.length ? section( - zh ? "配置之间的通过与失败变化" : "Pass and failure changes between configurations", + message("pass_and_failure_changes_between_configurations"), "", - relationText + `${esc(zh ? "查看对应的新增通过和失败用例" : "Inspect gained passes and failures")}`, + relationText + `${esc(message("inspect_gained_passes_and_failures"))}`, ) : "") + section( - zh ? "如何理解一个通过结果" : "What a passed case means", + message("what_a_passed_case_means"), casePopulation(), - `

${esc(zh ? "每个任务定义固定操作和验收条件。单次工具返回成功后,仍须由评分器检查是否满足任务要求。" : "Each task defines fixed actions and acceptance conditions. After a successful tool response, the evaluator checks whether the task requirements were met.")}

${example ? `

${esc(example.id)} · ${esc(methodLabel(selected.a))} ${example.passes[selected.a]}/${M.protocol.k_runs} → ${esc(methodLabel(selected.b))} ${example.passes[selected.b]}/${M.protocol.k_runs} ${esc(zh ? "检查任务要求、实际输出和评分证据" : "Inspect requirements, actual output and evaluator evidence")}

` : ""}`, + `

${esc(message("each_task_defines_fixed_actions_and_acceptance_conditions_after_a"))}

${example ? `

${esc(example.id)} · ${esc(methodLabel(selected.a))} ${example.passes[selected.a]}/${M.protocol.k_runs} → ${esc(methodLabel(selected.b))} ${example.passes[selected.b]}/${M.protocol.k_runs} ${esc(message("inspect_requirements_actual_output_and_evaluator_evidence"))}

` : ""}`, ) + candidateResourceSection() + section( - zh ? "适用范围与证据" : "Scope and evidence", + message("scope_and_evidence"), "", `

${esc(reproduceIntro())}

${esc(T.noHuman)}

${esc(T.nav.analysis)} · ${esc(T.nav.method)} · ${esc(T.nav.review)}`, ) @@ -1511,22 +1254,18 @@ function candidateReproductionSection() { return `${esc(zh ? method.label : method.label_en)}${esc(run.run_id)}${fmt(run.rows)}/2,785${esc(build.sha256)}`; }); return section( - zh - ? "1.1.9 Linux release 资源复现" - : "Reproduce 1.1.9 Linux release resources", - zh - ? "Mac 主机上的固定 Docker Linux 镜像;557 题、每题五次、jobs=1,250 ms PSS 采集间隔。测量前冻结任务、依赖和二进制哈希;各配置独立容器、串行执行。" - : "A fixed Docker Linux image on the Mac host; 557 tasks, five attempts each, jobs=1 and a 250 ms PSS interval. Tasks, dependencies and binary hashes are frozen before execution. Configurations run serially in independent containers.", + message("reproduce_1_1_9_linux_release_resources"), + message("a_fixed_docker_linux_image_on_the_mac_host_557_tasks_five_attempt"), table( [ - zh ? "配置" : "Configuration", - zh ? "测量 ID" : "Measurement ID", - zh ? "调用数" : "Calls", - zh ? "资源二进制 SHA-256" : "Resource binary SHA-256", + message("configuration"), + message("measurement_id"), + message("calls"), + message("resource_binary_sha_256"), ], rows, ) + - `

`, + `

`, ); } let methods = macosResourceMethods(); @@ -1536,22 +1275,20 @@ function candidateReproductionSection() { return `${esc(item.label)}${esc(run.run_id)}2,785/2,785${esc(run.manifest_sha256.slice(0, 16))}${esc(item.resource.summary.candidate.binary_sha256)}`; }); return section( - zh ? "Moli 1.1.9 资源运行身份" : "Moli 1.1.9 resource run identities", - zh - ? "每个固定配置执行 557 题 × 5 次;任务、种子与资源采样协议相同。" - : "Each fixed configuration runs 557 tasks × 5 calls with the same tasks, seed and sampling protocol.", + message("moli_1_1_9_resource_run_identities"), + message("each_fixed_configuration_runs_557_tasks_5_calls_with_the_same_tas"), table( [ - zh ? "配置" : "Configuration", - zh ? "运行 ID" : "Run ID", - zh ? "覆盖" : "Coverage", - zh ? "清单 SHA-256 前缀" : "Manifest SHA-256 prefix", - zh ? "二进制 SHA-256" : "Binary SHA-256", + message("configuration"), + message("run_id"), + message("coverage"), + message("manifest_sha_256_prefix"), + message("binary_sha_256"), ], rows, ) + (M.resource_matrix - ? `

` + ? `

` : ""), ); } @@ -1563,38 +1300,38 @@ function reproducePage() { return ( hero(T.nav.reproduce, reproduceIntro()) + section( - zh ? "运行身份" : "Run identities", + message("run_identities_0df143"), "", table( [ T.mode, - zh ? "运行 ID" : "Run ID", - zh ? "结果覆盖" : "Coverage", - zh ? "清单哈希前缀" : "Manifest hash prefix", + message("run_id"), + message("coverage_c8f31d"), + message("manifest_hash_prefix"), ], rows, ), ) + candidateReproductionSection() + section( - zh ? "复现要点" : "Reproduction controls", + message("reproduction_controls"), "", - `

${esc(zh ? "使用官方 Moli 1.1.8 Linux aarch64 二进制,核对 SHA-256。固定任务文件、种子、单工作进程、相同任务顺序和 Docker 镜像。两种模式分别执行无采样与 500 ms 采样轮,每题每轮三次,比较采样前后判定。" : "Use official Moli 1.1.8 Linux aarch64, verify SHA-256, and freeze tasks, seed, one-worker order and Docker image. Run each mode unprofiled and with 500 ms profiling, three attempts per case per round, then compare verdicts.")}

${facts( + `

${esc(message("use_official_moli_1_1_8_linux_aarch64_verify_sha_256_and_freeze_t"))}

${facts( [ [ - zh ? "宿主机" : "Host", + message("host"), esc( `${M.environment.macos.cpu} · ${M.environment.macos.logical_cpus} logical CPUs · ${M.environment.macos.memory_bytes / 1073741824} GiB`, ), ], [ - zh ? "Linux 虚拟机" : "Linux VM", + message("linux_vm"), esc( `${M.environment.linux_vm.virtualization} · ${M.environment.linux_vm.cpu_count} vCPU · ${fmt(M.environment.linux_vm.memory_bytes / 1073741824, 2)} GiB · aarch64`, ), ], [ - zh ? "容器镜像 SHA-256" : "Container image SHA-256", + message("container_image_sha_256"), `${esc(M.environment.image_id)}`, ], [ @@ -1602,34 +1339,34 @@ function reproducePage() { `${esc(M.binary_sha256)}`, ], [ - zh ? "任务清单 SHA-256" : "Task manifest SHA-256", + message("task_manifest_sha_256"), `${esc(M.manifest_sha256)}`, ], [ - zh ? "评测器源码 SHA-256" : "Runner source SHA-256", + message("runner_source_sha_256"), `${esc(M.protocol.runner_sha256)}`, ], [ - zh ? "测试站点 SHA-256" : "Fixture tree SHA-256", + message("fixture_tree_sha_256"), `${esc(M.protocol.fixtures_sha256)}`, ], - [zh ? "随机种子" : "Seed", `${esc(M.protocol.seed)}`], + [message("seed"), `${esc(M.protocol.seed)}`], [ - zh ? "每题调用次数与并发" : "Calls per task and concurrency", + message("calls_per_task_and_concurrency"), `${M.protocol.k_runs} / ${M.protocol.jobs}`, ], ], - )}

${esc(zh ? "下载冻结任务集" : "Download frozen cohort")} · ${esc(zh ? "下载复现脚本" : "Download reproduction script")}

${esc(zh ? "下载报告数据模型" : "Download report data model")}

`, + )}

${esc(message("download_frozen_cohort"))} · ${esc(message("download_reproduction_script"))}

${esc(message("download_report_data_model"))}

`, ) ); } function setNav(page) { document .getElementById("menu-toggle") - .setAttribute("aria-label", zh ? "展开或收起导航" : "Toggle navigation"); + .setAttribute("aria-label", message("toggle_navigation")); document .getElementById("site-nav") - .setAttribute("aria-label", zh ? "报告导航" : "Report navigation"); + .setAttribute("aria-label", message("report_navigation")); document.getElementById("site-nav").innerHTML = Object.entries(T.nav) .map( ([k, v]) => @@ -1638,23 +1375,15 @@ function setNav(page) { .join(""); let other = zh ? "en/index.html" : "index.html"; document.getElementById("language").href = base + other + location.hash; - document.getElementById("language").textContent = zh ? "EN" : "中文"; - document.querySelector(".skip").textContent = zh - ? "跳到正文" - : "Skip to content"; + document.getElementById("language").textContent = message("language_label"); + document.querySelector(".skip").textContent = message("skip_to_content"); document .getElementById("theme") - .setAttribute("aria-label", zh ? "切换明暗主题" : "Toggle theme"); + .setAttribute("aria-label", message("toggle_theme")); document.getElementById("dialog-close").setAttribute("aria-label", T.close); - document.querySelector(".brand small").textContent = zh - ? "/ 版本与布局" - : "/ Versions and layout"; - document.querySelector(".footer span").textContent = zh - ? `${reportVersion()} · 成功率与资源实验` - : `${reportVersion()} · compatibility and resource study`; - document.querySelector(".footer a").textContent = zh - ? "方法与证据" - : "Method & evidence"; + document.querySelector(".brand small").textContent = message("versions_and_layout"); + document.querySelector(".footer span").textContent = message("compatibility_and_resource_study", {value0: (reportVersion())}); + document.querySelector(".footer a").textContent = message("method_evidence"); } async function render() { let r = route(), @@ -1742,7 +1471,7 @@ async function showDiagnosis(button) { let id = button.dataset.diagnosis, mode = button.dataset.mode; document.getElementById("evidence-title").textContent = - (zh ? "失败诊断" : "Failure diagnosis") + " · " + T.modes[mode]; + (message("failure_diagnosis")) + " · " + T.modes[mode]; document.getElementById("evidence-body").textContent = T.loading; dialog.showModal(); try { @@ -1757,10 +1486,10 @@ async function showDiagnosis(button) { [T.task, "" + esc(id) + ""], [T.attempt.replace("{n}", a.ordinal), esc(T.modes[mode])], [ - zh ? "程序分类" : "Program classification", + message("program_classification"), esc(a.failure.class || T.unavailable), ], - [zh ? "独立复核原因" : "Independently reviewed cause", esc(T.noHuman)], + [message("independently_reviewed_cause"), esc(T.noHuman)], ]) + '
' + esc(a.failure.detail || T.unavailable) + @@ -1769,7 +1498,7 @@ async function showDiagnosis(button) { "&attempt=" + a.ordinal + '">' + - esc(zh ? "完整结果与对应调用" : "Full result and exact attempt") + + esc(message("full_result_and_exact_attempt")) + ' →

"; @@ -1803,7 +1532,7 @@ document.addEventListener("click", async (e) => { if (!resp.ok) throw Error("Evidence unavailable"); let raw = await resp.text(); document.getElementById("evidence-body").innerHTML = - `

SHA-256: ${esc(b.dataset.sha)}

${esc(raw)}

${esc(T.source)} ↗

`; + `

SHA-256: ${esc(b.dataset.sha)}

${esc(raw)}

${esc(T.source)} ↗

`; let field = document.getElementById("evidence-query"), content = document.getElementById("evidence-text"); let find = () => { @@ -1819,12 +1548,8 @@ document.addEventListener("click", async (e) => { esc(raw.slice(index + needle.length)); document.getElementById("evidence-matches").textContent = needle ? index < 0 - ? zh - ? "未找到" - : "No match" - : zh - ? "已定位首处" - : "First match located" + ? message("no_match") + : message("first_match_located") : ""; content.querySelector("mark")?.scrollIntoView({ block: "center" }); }; diff --git a/scripts/moli_report/index.html b/scripts/moli_report/index.html index 558caad44..b1a6afa76 100644 --- a/scripts/moli_report/index.html +++ b/scripts/moli_report/index.html @@ -7,6 +7,7 @@ Moli · Success and resources + diff --git a/scripts/moli_report/messages.json b/scripts/moli_report/messages.json new file mode 100644 index 000000000..6b8b721cb --- /dev/null +++ b/scripts/moli_report/messages.json @@ -0,0 +1,744 @@ +{ + "zh": { + "nav": { + "home": "总览", + "dataset": "评测集", + "method": "评测方法", + "analysis": "结果分析", + "results": "逐题结果", + "review": "证据与可靠性", + "guide": "报告解读", + "reproduce": "环境与复现" + }, + "modes": { + "off": "无布局", + "on": "开布局" + }, + "strata": { + "raw_read": "原始 CDP 读取", + "raw_geometry_input": "几何与坐标输入", + "playwright": "Playwright 常规操作", + "puppeteer": "Puppeteer 常规操作", + "agent_tool": "Agent 工具", + "multi_step": "多步交互", + "agent_scenario": "Agent 场景", + "node_cdp_probe": "Node CDP", + "raw_cdp": "Raw CDP", + "tool_agent_browser": "agent-browser", + "thin_cdp_use": "cdp-use", + "thin_chromedp": "chromedp", + "thin_chrome_remote_interface": "chrome-remote-interface", + "thin_ferrum": "Ferrum", + "mcp_chrome_devtools": "Chrome DevTools MCP", + "thin_chromiumoxide": "Chromiumoxide", + "framework_puppeteer": "Puppeteer", + "framework_playwright": "Playwright", + "thin_pydoll": "Pydoll", + "thin_rod": "Rod", + "framework_stagehand": "Stagehand", + "webdriver_selenium": "Selenium" + }, + "pass": "通过", + "fail": "失败", + "all": "全部", + "open": "查看", + "evidence": "证据", + "missing": "暂无证据", + "loading": "正在载入…", + "error": "数据读取失败", + "noHuman": "未进行独立人工逐次复核", + "methodLimit": "覆盖 Moli 0.1.1 的全部 372 个所选用例;结果适用于这一失败子集。", + "more": "查看详情", + "task": "任务", + "mode": "模式", + "attempt": "第 {n} 次", + "status": "状态", + "duration": "耗时", + "cpu": "CPU 时间", + "pss": "结束 PSS", + "traffic": "站点下行", + "failure": "程序错误", + "source": "脱敏证据", + "search": "搜索任务、驱动或程序错误", + "relation": "结果关系", + "stratum": "任务类型", + "bothPass": "两种模式均通过", + "gain": "开启后新增通过用例", + "mixed": "部分通过,次数相同", + "bothFail": "两种模式均失败", + "loss": "开启后退步用例", + "back": "返回逐题结果", + "close": "关闭", + "unavailable": "未提供", + "start": "查看逐题结果", + "method": "查看方法", + "metricNote": "P50 是中位数;P95 是第 95 百分位。耗时为驱动执行与验收的墙钟时间,不含浏览器启动、外围目标检查及最终证据落盘。", + "winner": "较低", + "higher": "较高", + "scope": "适用范围", + "observed": "观测到的采样最大值", + "filterFail": "涉及失败或变化", + "methodIntro": "题目与验收预先冻结,同一构建的两种布局配置只相差是否启用 --layout。默认模式使用低成本模拟几何;--layout 启用按需真实布局,并非持续绘制。可选图片等资源抓取由另一开关控制,未改变。功能与资源独立测量,任务范围和执行次数分别见对应配置表。", + "analysisIntro": "按相同任务与执行次数配对。成功变化和资源代价分开解读。", + "guideIntro": "从 Agent 使用者角度,先看能否完成,再看失败等待和完成任务的资源。", + "details": "展开证据", + "calibration": "采样校准", + "resultCount": "显示 {n} / {total} 个任务", + "rawContract": "原始任务合同", + "noRows": "没有符合筛选条件的任务。", + "messages": { + "the_same_preselected_tasks_calls_each_and_calls_per_mode": "同一组预先固定的 {value0} 个任务,每种模式各运行 {value1} 次,共 {value2} 次调用。", + "pass_counts_use_cases_a_case_passes_only_when_all_recorded_unprof": "通过数按 {value0} 个用例统计:每题已记录的 {value1} 次无采样执行全部通过,才计为 1 个通过用例。", + "all_predeclared_calls_including_failures_mean_p50_and_p95_use_the": "全部 {value0} 次调用,成功与失败都计入;平均、P50 和 P95 使用同一组调用。", + "compare_fixed_configurations_on_selected_cases_with_separate_reso": "比较 {value0} 种固定配置在 {value1} 个所选用例上的成功率,并展示独立资源实验。", + "compatibility_and_resources": "{value0} · 成功率与资源对比", + "all_372_cases_selected_from_moli_0_1_1_failures_are_included_acro": "依据 Moli 0.1.1 原始结果选取全部 372 个失败用例,覆盖 15 类驱动;不使用结果决定入选。", + "functional_cases_directly_tested_cases_have_recorded_attempts_eac": "{value0} 个功能用例;直接测试的用例每题记录 {value1} 次执行。各配置实际测试的范围和沿用既有证据的范围见表。本报告为诊断实验。资源测量环境:{value2}。功能和资源测试各自记录浏览器的二进制哈希;通过率对应功能测试使用的构建。", + "always_on": "全程开启", + "always_off": "全程关闭", + "direct": "题实测", + "shared_failures": "题继承共同失败关系", + "direct_cases": "题实测", + "fixed_layout_functional_evidence": "固定布局功能证据", + "inspect_case_evidence": "查看逐题证据", + "baseline": " · 基线", + "pp": " 个百分点", + "success_rates_for_fixed_configurations": "{value0} 种固定配置的成功率", + "cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s": "{value0} 题,每题 3/3 通过才算成功;资源列使用固定 209 题样本,统一为 Linux 进程树 PSS 与 CPU 时间。基线:1.1.4 关闭 layout;绿色为唯一最优值。", + "cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s_49ab3d": "{value0} 题,每题 3/3 通过才算成功;资源列使用固定 209 题样本。基线:1.1.4 关闭 layout。— 表示执行环境不同,不计算资源涨幅;绿色为同环境唯一最优值。", + "method": "方法", + "layout": "布局", + "evidence_scope": "证据范围", + "passed_cases": "通过用例", + "success_rate_change": "通过率变化", + "calls": "调用数", + "peak_memory_p50_baseline_change": "峰值内存 P50 / 基线变化", + "mean_cpu_baseline_change": "CPU 均值 / 基线变化", + "fixed_configurations_aligned_with_the_summary_table_baseline_1_1_": "{value0} 种固定配置的成功率与资源,顺序与总表一致,以 1.1.4 关闭布局为基线", + "resources_are_compared_within_each_execution_environment_function": "资源按执行环境分别比较。功能和资源测试各自记录浏览器构建;通过率对应功能测试使用的构建,哈希和证据见评测方法。", + "all_calls_on_557_tasks": "557 题全部调用", + "chart_sample_209_tasks": "图表样本:209 题", + "peak_rss_mean": "峰值 RSS 均值", + "peak_rss_p50": "峰值 RSS P50", + "peak_rss_p95": "峰值 RSS P95", + "cpu_mean": "CPU 均值", + "tie": "并列", + "lowest_peak_rss_p50_lowest_mean_cpu_per_call_resource_minima_and_": "峰值 RSS P50 最低:{value0};单次 CPU 均值最低:{value1}。资源最低与通过率最高分别判断。", + "peak_rss_p50_change": "峰值 RSS P50 变化", + "cpu_mean_change": "CPU 均值变化", + "moli_1_1_9_with_the_same_layout_setting": "相同 layout 设置的 Moli 1.1.9", + "resources_are_measured_separately_from_success_on_372_cases_each_": "资源实验与 372 题成功率测试分开:每个配置固定 layout,557 题各执行 5 次。P50 是中位数,P95 是第 95 百分位;每组统计包含同一批成功与失败调用。变化基准:{value0}。macOS 测量进程树 RSS;Linux PSS 另表展示。", + "patched_resource_evidence": "修复版资源证据", + "1_1_9_resource_evidence": "1.1.9 资源证据", + "matched_resource_call_matrix": "资源对照调用矩阵", + "serial_comparison_and_build_evidence": "串行对照与构建证据", + "moli_1_1_9_resource_statistics": "Moli 1.1.9 资源统计", + "all_four_release_configurations_share_one_host_and_retain_separat": "四组均使用 release 构建,共享同一主机,各自保留独立浏览器进程和采样。任务调用按配置轮换、串行执行,只统计当前配置的进程树。CPU 为用户态与内核态时间之和。", + "peak_pss_mean": "峰值 PSS 均值", + "peak_pss_p50": "峰值 PSS P50", + "peak_pss_p95": "峰值 PSS P95", + "lowest_peak_pss_p50_lowest_mean_cpu_per_call_resource_consumption": "峰值 PSS P50 最低:{value0};单次 CPU 均值最低:{value1}。资源消耗与通过率分别比较。", + "pss_p50_change": "PSS P50 变化", + "mean_cpu_change": "CPU 均值变化", + "1_1_4_1_1_8_resource_evidence": "1.1.4 / 1.1.8 资源证据", + "1_1_9_measurements_and_calibration": "1.1.9 测量与校准证据", + "resource_statistics_for_seven_configurations": "七种配置的资源统计", + "all_metrics_include_the_same_passing_and_failing_calls_p50_is_the": "各指标使用同一批成功与失败调用;P50 为中位数,P95 为第 95 百分位。全部变化以 1.1.4 关闭 layout 为基准。", + "resource_measurement_and_comparability": "资源测量与可比范围", + "cpu_metric": "CPU 指标", + "mean_per_call_cpu": "单次 CPU 均值", + "total_cpu_time": "累计 CPU 时间", + "per_call_cpu_p50": "单次 CPU P50", + "per_call_cpu_p95": "单次 CPU P95", + "memory_metric": "内存指标", + "mean_observed_pss_maximum": "观测 PSS 最大值均值", + "observed_pss_maximum_p50": "观测 PSS 最大值 P50", + "observed_pss_maximum_p95": "观测 PSS 最大值 P95", + "pss_increase_p50": "PSS 增量 P50", + "fixture_application_traffic": "站点应用层流量", + "total_downlink": "下行总量", + "downlink_p50": "下行 P50", + "total_uplink": "上行总量", + "uplink_p50": "上行 P50", + "cold_start_metric_off_on": "冷启动指标 · 关闭 {value0} / 开启 {value1} 次", + "ready_time_p50": "就绪时间 P50", + "launch_cpu_p50": "启动 CPU P50", + "launch_observed_pss_p50": "启动观测 PSS P50", + "tie_f4719e": "持平", + "observed_profiled_resource_values": "采样执行中的资源数值", + "all_call_cpu_observed_pss_maximum_p50_fixture_downlink_these_are_": "全部调用 CPU:{value0};观测 PSS 最大值 P50:{value1};站点下行:{value2}。这些是采样执行的观测值;主耗时采用无采样轮。", + "cpu_and_computation": "CPU 与计算", + "memory": "内存", + "network_traffic": "网络流量", + "cold_start": "冷启动", + "tasks_used_different_definition_versions_across_configurations_th": "{value0} 题在不同配置中使用了不同版本的任务定义,其余 {value1} 题定义一致。表中保留各配置按当时任务版本取得的成绩。", + "inspect_resource_statistics_and_measurement_scope": "查看完整资源统计与测量范围", + "explore_the_results": "如何查看结果", + "compare_gained_passes_and_remaining_failures": "比较新增通过与仍然失败的用例", + "accepted_call_duration": "成功调用耗时", + "mean_accepted_call_duration": "成功调用平均耗时", + "accepted_call_p50_duration": "成功调用 P50 耗时", + "accepted_call_p95_duration": "成功调用 P95 耗时", + "tasks": "题", + "family": "任务类别", + "layout_on_pass_share": "开布局通过占比", + "find_and_filter": "查找与筛选", + "selection_rule": "选择规则", + "subset": "子集", + "attempt": "序号", + "off_status": "无布局状态", + "on_status": "开布局状态", + "off_duration": "无布局耗时", + "on_duration": "开布局耗时", + "off_cpu": "无布局 CPU", + "on_cpu": "开布局 CPU", + "off_ending_pss": "无布局结束 PSS", + "on_ending_pss": "开布局结束 PSS", + "task_id": "任务 ID", + "task_sha_256": "任务定义 SHA-256", + "features": "协议特性", + "case_verdict": "用例判定", + "same_task_results": "同题结果", + "paired_attempts": "逐次配对", + "duration_comes_from_the_unprofiled_run_cpu_and_ending_pss_come_fr": "耗时来自无采样轮;CPU 和结束 PSS 来自同题同次的资源采样轮。点击状态可直达对应调用的证据。", + "driver_steps": "驱动步骤", + "acceptance_contract": "验收合同", + "scene": "场景", + "grader": "评分规则", + "unprofiled": "无采样", + "profiled": "资源采样", + "verdict_and_response": "评分与响应", + "profiled_verdict": "采样轮状态", + "answer": "响应", + "moli_resources": "Moli 资源", + "observed_pss_maximum": "观测 PSS 最大值", + "samples": "采样点", + "attempts_passed_all_must_pass": "{value0}/{value1} 次执行通过;全部通过才计通过用例", + "total_for_calls": "{value0} 次总耗时", + "compared_with_the_other_mode": "与另一模式比较", + "failure_locator": "失败定位", + "all_calls_passed_in_this_mode": "本模式 {value0} 次调用均通过。", + "result_summary": "结果摘要", + "run_identities": "运行标识", + "unprofiled_run": "无采样轮", + "profiled_run": "资源采样轮", + "calls_and_evidence": "{value0} 次调用与证据", + "each_call_pairs_the_unprofiled_verdict_and_duration_with_the_prof": "每次调用保留无采样的结果与耗时,以及同题同次资源采样值。点击证据可查看原始评分、输出、日志或 CDP 消息。", + "other_mode": "另一模式", + "fixed_tasks_fixed_layout_and_a_separate_resource_experiment": "固定任务、固定布局与独立资源实验。", + "evaluation_protocol": "评测流程", + "freeze_tasks_binaries_and_acceptance_rules": "冻结任务、二进制与验收规则", + "fix_version_and_layout_configuration": "固定版本与 layout 配置", + "functional_372_cases_three_attempts_each": "功能:372 题,每题 3 次", + "resources_557_tasks_five_calls_each": "资源:557 题,每题 5 次", + "a_case_passes_only_when_all_three_attempts_pass": "三次全部通过才算通过", + "all_call_statistics_and_fixed_209_task_chart_sample": "全部调用统计与固定 209 题图表样本", + "configuration_table_and_chart": "配置总表与总图", + "functional_scoring_and_baseline": "功能评分与基线", + "all_configurations_use_the_same_372_case_denominator_a_case_passe": "所有配置共用 372 题分母。每题三次执行全部通过运行与验收检查,才记为一个通过用例。1.1.8 的两种布局、修复版 1.1.9 的两种布局和 1.1.9 的两种布局均覆盖全部 372 题。", + "layout_off_1_1_4_is_the_baseline_the_sole_candidate_passed_by_lay": "1.1.4 关闭布局为基线:对 1.1.8 关闭布局唯一通过的候选题实测三次,均失败;其余 371 题沿用共同失败关系。因此表中的 0/372 包含 1 题直接验证和 371 题继承结果,并非对 1.1.4 的完整重测。基线通过率为 0%,通过率变化用百分点表示。", + "task_selection_and_scope": "任务来源与适用范围", + "the_372_cases_are_all_cases_from_the_1_928_case_suite_that_moli_0": "372 题选自 Moli 0.1.1 在 1,928 题中未达到三次全部通过的全部用例,覆盖 15 类驱动。Chrome 结果不影响入选。其余 1,556 题不在评测范围内,这里的通过率不代表完整 1,928 题的通过率。", + "resources_use_a_separate_557_task_experiment_with_five_calls_per_": "资源实验单独执行 557 题,每题五次,每种配置共 2,785 次调用。图和总表使用运行前固定的 209 题、1,045 次样本;完整资源表另列全部 557 题。P50 是中位数,P95 是第 95 百分位。", + "moli_1_1_4_1_1_8_run_in_docker_linux_containers_launched_from_the": "1.1.4 / 1.1.8 在 Mac 发起的 Docker Linux 容器中执行,使用 Linux ARM64 二进制并采集 PSS;1.1.9 在 macOS 原生执行,使用 Mach-O ARM64 二进制并采集 RSS。七组均为本次系列的复测数据,非官网 71 MiB 数据;同一宿主机不等于相同执行环境。CPU 为进程树用户态与内核态时间之和。跨平台的内存和 CPU 不计算相对 1.1.4 的百分比变化;同平台百分比是样本数值对照。四组 macOS 资源配置均为 release 构建,使用同一冻结调用矩阵,逐次轮换顺序并串行执行。", + "rss_and_pss_memory_definitions": "RSS 与 PSS:指标口径", + "both_use_mib_but_account_for_shared_memory_differently_and_cannot": "两者均以 MiB 展示,但共享内存的计数方式不同,不能互换或按固定比例换算。", + "property": "对比项", + "what_is_counted": "统计内容", + "pages_currently_resident_in_physical_memory_for_the_process": "进程当前驻留物理内存中的页", + "private_resident_pages_plus_a_proportional_share_of_shared_reside": "私有驻留页,加上按共享进程数分摊的共享驻留页", + "shared_pages": "共享页", + "counted_for_each_mapping_process_process_tree_sums_can_count_shar": "每个映射进程都计入;进程树求和会重复计入共享页", + "counted_proportionally_reducing_double_counting_across_processes": "按共享份额计入,减少进程间重复计算", + "example": "示例", + "20_mib_private_plus_10_mib_shared_equally_with_one_other_process_": "20 MiB 私有页+与另一进程共享的 10 MiB:RSS=30 MiB", + "same_example_pss_20_10_2_25_mib": "同例:PSS=20+10÷2=25 MiB", + "collection_in_this_report": "本报告采集", + "this_report_consistently_compares_pss": "本报告资源对比统一使用 PSS", + "macos_ps_rss_field_summed_over_the_process_tree": "macOS:ps 的 rss 字段,按进程树求和", + "linux_pss_in_proc_pid_smaps_rollup_summed_over_the_process_tree": "Linux:/proc/PID/smaps_rollup 的 Pss 字段,按进程树求和", + "comparison_conditions": "比较条件", + "matching_execution_environment_sampling_and_task_scope_cpu_compar": "相同执行环境、采样与任务范围;CPU 时间也须采用一致条件", + "matching_conditions_and_shared_process_environment_pss_is_not_rss": "相同条件,且保持共享进程环境一致;PSS 不等于 RSS", + "linux_kernel_rss_and_pss_definitions": "Linux 内核:RSS 与 PSS 定义", + "apple_resident_memory_and_shared_mappings": "Apple:驻留内存与共享映射", + "execution_and_evidence": "执行与证据", + "the_resource_experiment_executes_one_task_call_globally_at_a_time": "资源实验全局同时只执行一次任务调用。功能与资源分别绑定构建身份;成功率适用于对应的功能构建。任务定义、程序判定、标准输出与错误日志可从逐题详情检查。未进行独立人工逐次复核。", + "where_the_time_reduction_came_from": "耗时下降来自哪些任务", + "these_three_tasks_account_for_of_the_net_duration_change_positive": "以下三题贡献净耗时变化的 {value0}%。正值表示开启布局后耗时下降;负值表示增加。CPU 差值来自开启资源采集的调用,耗时差值来自关闭资源采集的调用。", + "call_duration": "{value0} 次总耗时", + "time_reduction": "耗时减少", + "cpu_reduction": "CPU 减少", + "same_patched_build_enabling_layout": "同一修复版:开启 layout 的变化", + "layout_on_patched_vs_1_1_9": "同为开启 layout:修复版与 1.1.9", + "1_1_9_enabling_layout": "1.1.9:开启 layout 的变化", + "layout_changes_and_version_changes": "布局变化与版本变化", + "compare_layout_off_on_within_the_patched_build_to_measure_layout_": "同一修复版的开关对照用于判断布局变化;固定开启 layout 的 1.1.9 与修复版对照用于定位版本间变化。下表保留新增通过和退步,不能只看净增加。", + "comparison": "对照", + "gained": "新增通过", + "lost": "退步", + "both_pass": "均通过", + "both_fail": "均失败", + "outcome_relation": "结果关系", + "passed_attempts": "通过次数", + "task_requirements_for_this_comparison": "任务要求", + "inspect_frozen_task_definition": "查看冻结任务定义", + "same_task_version_comparison": "同题版本对照", + "view_1_1_8_task_requirements_and_results": "查看 1.1.8 任务要求与结果", + "moli_1_1_8_case_outcomes": "1.1.8 用例通过关系", + "cases_gained": "新增通过的用例", + "cases_lost": "退步的用例", + "failure_evidence": "失败证据", + "open_the_failure_filter_for_exact_program_errors_and_full_logs": "打开失败筛选,查看每题的程序错误和完整日志。", + "by_task_family": "任务类型比较", + "pass": "通过", + "fail": "失败", + "1_1_9_resource_measurement_quality": "1.1.9 资源测量质量", + "the_official_procedure_pairs_profiler_off_calibration_with_profil": "按官方流程执行 profiler-off 校准和 profiler-on 测量;核验完整题目范围、构建身份、PSS/CPU 覆盖、采集器影响和宿主机负载。", + "configuration": "配置", + "pss_coverage": "PSS 覆盖", + "cpu_coverage": "CPU 覆盖", + "profiler_calibration": "采集器校准", + "host_check": "宿主机检查", + "failed": "未通过", + "moli_1_1_9_resource_evidence_quality": "Moli 1.1.9 资源证据质量", + "each_fixed_configuration_has_one_measured_run_checks_cover_the_co": "每个固定配置测一轮,核验完整任务矩阵、二进制身份、资源覆盖与宿主机观测。未单独测量采样器带来的耗时影响。", + "rss_coverage": "RSS 覆盖", + "verify_evaluation_coverage_binary_identities_and_evidence_sources": "核对评测覆盖、二进制身份与证据来源。", + "functional_evidence_coverage": "功能证据覆盖", + "direct_cases_5505c5": "直接验证题数", + "inherited_cases": "继承结果题数", + "scoring_and_review_coverage": "评分与复核范围", + "outcomes_come_from_programmatic_evaluation_against_frozen_accepta": "结果来自冻结验收规则的程序判定。报告检查调用矩阵、版本和任务哈希、资源样本数、聚合数值及证据文件完整性。未进行独立人工逐次复核;程序通过结果不等同于人工审查结论。", + "download_the_data_model_and_evidence_index": "下载完整数据与证据索引", + "pass_and_failure_changes_between_configurations": "配置之间的通过与失败变化", + "inspect_gained_passes_and_failures": "查看对应的新增通过和失败用例", + "what_a_passed_case_means": "如何理解一个通过结果", + "each_task_defines_fixed_actions_and_acceptance_conditions_after_a": "每个任务定义固定操作和验收条件。单次工具返回成功后,仍须由评分器检查是否满足任务要求。", + "inspect_requirements_actual_output_and_evaluator_evidence": "检查任务要求、实际输出和评分证据", + "scope_and_evidence": "适用范围与证据", + "reproduce_1_1_9_linux_release_resources": "1.1.9 Linux release 资源复现", + "a_fixed_docker_linux_image_on_the_mac_host_557_tasks_five_attempt": "Mac 主机上的固定 Docker Linux 镜像;557 题、每题五次、jobs=1,250 ms PSS 采集间隔。测量前冻结任务、依赖和二进制哈希;各配置独立容器、串行执行。", + "measurement_id": "测量 ID", + "resource_binary_sha_256": "资源二进制 SHA-256", + "resource_measurement_script": "资源测量脚本", + "inspect_the_resource_measurement_script": "查看资源测量脚本", + "moli_1_1_9_resource_run_identities": "Moli 1.1.9 资源运行身份", + "each_fixed_configuration_runs_557_tasks_5_calls_with_the_same_tas": "每个固定配置执行 557 题 × 5 次;任务、种子与资源采样协议相同。", + "run_id": "运行 ID", + "coverage": "覆盖", + "manifest_sha_256_prefix": "清单 SHA-256 前缀", + "binary_sha_256": "二进制 SHA-256", + "inspect_the_script_used_for_these_resource_measurements": "查看本次资源测量使用的脚本", + "run_identities_0df143": "运行身份", + "coverage_c8f31d": "结果覆盖", + "manifest_hash_prefix": "清单哈希前缀", + "reproduction_controls": "复现要点", + "use_official_moli_1_1_8_linux_aarch64_verify_sha_256_and_freeze_t": "使用官方 Moli 1.1.8 Linux aarch64 二进制,核对 SHA-256。固定任务文件、种子、单工作进程、相同任务顺序和 Docker 镜像。两种模式分别执行无采样与 500 ms 采样轮,每题每轮三次,比较采样前后判定。", + "host": "宿主机", + "linux_vm": "Linux 虚拟机", + "container_image_sha_256": "容器镜像 SHA-256", + "task_manifest_sha_256": "任务清单 SHA-256", + "runner_source_sha_256": "评测器源码 SHA-256", + "fixture_tree_sha_256": "测试站点 SHA-256", + "seed": "随机种子", + "calls_per_task_and_concurrency": "每题调用次数与并发", + "download_frozen_cohort": "下载冻结任务集", + "download_reproduction_script": "下载复现脚本", + "download_report_data_model": "下载报告数据模型", + "toggle_navigation": "展开或收起导航", + "report_navigation": "报告导航", + "language_label": "EN", + "skip_to_content": "跳到正文", + "toggle_theme": "切换明暗主题", + "versions_and_layout": "/ 版本与布局", + "compatibility_and_resource_study": "{value0} · 成功率与资源实验", + "method_evidence": "方法与证据", + "failure_diagnosis": "失败诊断", + "program_classification": "程序分类", + "independently_reviewed_cause": "独立复核原因", + "full_result_and_exact_attempt": "完整结果与对应调用", + "find_in_evidence": "查找证据", + "no_match": "未找到", + "first_match_located": "已定位首处", + "summary_chart": "{value0} 种固定配置的成功率与资源,顺序与总表一致,基线:{value1}", + "outcome_change_summary": "新增通过 {value0} 题,退步 {value1} 题,通过数净变化 {value2} 题。" + }, + "resource_methodology": [ + "资源测试独立使用 557 题,每题执行五次,每种配置共 2,785 次调用。图和总表使用事先固定的 209 题、1,045 次调用;完整资源表另列全部 557 题。每个统计范围都保留成功与失败调用。", + "七种配置都在 Mac 主机上的 Docker Linux 环境中测量,使用 Linux ARM64 release 二进制和同一资源采集方法。PSS 通过 /proc/PID/smaps_rollup 采集并按进程树求和,CPU 为该进程树的用户态与内核态 CPU 时间之和。250 ms 采集间隔,jobs=1,各配置串行执行。", + "官方流程先执行 profiler-off 校准,再执行 profiler-on 测量;报告资源值来自后者,校准用于检查采集器影响。P50 是中位数,P95 是第 95 百分位。全部资源变化以 1.1.4 关闭 layout 为基线,表示对应样本统计值的差异。官网 71 MiB 不参与这七组计算。" + ] + }, + "en": { + "nav": { + "home": "Overview", + "dataset": "Dataset", + "method": "Method", + "analysis": "Analysis", + "results": "Task results", + "review": "Evidence quality", + "guide": "Interpretation", + "reproduce": "Environment" + }, + "modes": { + "off": "Layout off", + "on": "Layout on" + }, + "strata": { + "raw_read": "Raw CDP reading", + "raw_geometry_input": "Geometry and input", + "playwright": "Playwright operations", + "puppeteer": "Puppeteer operations", + "agent_tool": "Agent tools", + "multi_step": "Multi-step flows", + "agent_scenario": "Agent scenarios", + "node_cdp_probe": "Node CDP", + "raw_cdp": "Raw CDP", + "tool_agent_browser": "agent-browser", + "thin_cdp_use": "cdp-use", + "thin_chromedp": "chromedp", + "thin_chrome_remote_interface": "chrome-remote-interface", + "thin_ferrum": "Ferrum", + "mcp_chrome_devtools": "Chrome DevTools MCP", + "thin_chromiumoxide": "Chromiumoxide", + "framework_puppeteer": "Puppeteer", + "framework_playwright": "Playwright", + "thin_pydoll": "Pydoll", + "thin_rod": "Rod", + "framework_stagehand": "Stagehand", + "webdriver_selenium": "Selenium" + }, + "pass": "Pass", + "fail": "Fail", + "all": "All", + "open": "Open", + "evidence": "Evidence", + "missing": "No evidence available", + "loading": "Loading…", + "error": "Could not load data", + "noHuman": "No independent per-call review", + "methodLimit": "This cohort includes all 372 cases selected from Moli 0.1.1 failures; results apply to this failure subset.", + "more": "View details", + "task": "Task", + "mode": "Mode", + "attempt": "Attempt {n}", + "status": "Status", + "duration": "Duration", + "cpu": "CPU time", + "pss": "Ending PSS", + "traffic": "Fixture downlink", + "failure": "Program error", + "source": "Sanitized evidence", + "search": "Search task, driver, or program error", + "relation": "Outcome relation", + "stratum": "Task family", + "bothPass": "Both modes pass", + "gain": "Cases gained with layout", + "mixed": "Equal partial passes", + "bothFail": "Both modes fail", + "loss": "Cases lost with layout", + "back": "Back to task results", + "close": "Close", + "unavailable": "Unavailable", + "start": "Explore task results", + "method": "Read methodology", + "metricNote": "P50 is the median and P95 the 95th percentile. Duration covers driver execution and grading, excluding browser startup, outer target checks and final evidence writing.", + "winner": "Lower", + "higher": "Higher", + "scope": "Scope", + "observed": "Observed sampled maximum", + "filterFail": "Failures or changes", + "methodIntro": "Task contracts were frozen in advance. Within each build, the two layout configurations differ only in Moli's --layout flag. The default uses low-cost mock geometry; --layout enables real geometry on demand rather than continuous painting. Optional image and media fetching is a separate control and was unchanged. Functional and resource experiments declare their task populations and attempt counts separately in the configuration tables.", + "analysisIntro": "Pair the same task and attempt. Outcome changes and resource costs are interpreted separately.", + "guideIntro": "For an agent user, completion comes first, followed by failure waiting time and cost on completed tasks.", + "details": "Expand evidence", + "calibration": "Profiling calibration", + "resultCount": "Showing {n} / {total} tasks", + "rawContract": "Raw task contract", + "noRows": "No tasks match these filters.", + "messages": { + "the_same_preselected_tasks_calls_each_and_calls_per_mode": "The same {value0} preselected tasks, {value1} calls each and {value2} calls per mode.", + "pass_counts_use_cases_a_case_passes_only_when_all_recorded_unprof": "Pass counts use {value0} cases: a case passes only when all {value1} recorded unprofiled attempts pass.", + "all_predeclared_calls_including_failures_mean_p50_and_p95_use_the": "All {value0} predeclared calls, including failures; Mean, P50 and P95 use the same population.", + "compare_fixed_configurations_on_selected_cases_with_separate_reso": "Compare {value0} fixed configurations on {value1} selected cases, with separate resource experiments.", + "compatibility_and_resources": "{value0} · Compatibility and resources", + "all_372_cases_selected_from_moli_0_1_1_failures_are_included_acro": "All 372 cases selected from Moli 0.1.1 failures are included across 15 driver types, without selection based on current outcomes.", + "functional_cases_directly_tested_cases_have_recorded_attempts_eac": "{value0} functional cases; directly tested cases have {value1} recorded attempts each. The configuration table identifies directly tested cases and verdicts retained from existing evidence. This is a diagnostic experiment. Resource environments: {value2}. Functional and resource experiments bind their binary identities separately; success rates apply to the corresponding functional build.", + "always_on": "Always on", + "always_off": "Always off", + "direct": "direct", + "shared_failures": "shared failures", + "direct_cases": "direct cases", + "fixed_layout_functional_evidence": "Fixed-layout functional evidence", + "inspect_case_evidence": "Inspect case evidence", + "baseline": " · baseline", + "pp": " pp", + "success_rates_for_fixed_configurations": "Success rates for {value0} fixed configurations", + "cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s": "{value0} cases; all 3 attempts must pass. Resources use the fixed 209-task sample and Linux process-tree PSS and CPU time. Baseline: 1.1.4 layout off; green marks each unique optimum.", + "cases_all_3_attempts_must_pass_resources_use_the_fixed_209_task_s_49ab3d": "{value0} cases; all 3 attempts must pass. Resources use the fixed 209-task sample. Baseline: 1.1.4 layout off. — denotes incompatible execution environments; green marks the unique optimum within each environment.", + "method": "Method", + "layout": "Layout", + "evidence_scope": "Evidence scope", + "passed_cases": "Passed cases", + "success_rate_change": "Success-rate change", + "calls": "Calls", + "peak_memory_p50_baseline_change": "Peak memory P50 / baseline change", + "mean_cpu_baseline_change": "Mean CPU / baseline change", + "fixed_configurations_aligned_with_the_summary_table_baseline_1_1_": "{value0} fixed configurations, aligned with the summary table; baseline: 1.1.4 layout off", + "resources_are_compared_within_each_execution_environment_function": "Resources are compared within each execution environment. Functional and resource experiments bind build identities separately; success rates apply to the corresponding functional build. See the method page for build identities and evidence.", + "all_calls_on_557_tasks": "All calls on 557 tasks", + "chart_sample_209_tasks": "Chart sample: 209 tasks", + "peak_rss_mean": "Peak RSS mean", + "peak_rss_p50": "Peak RSS P50", + "peak_rss_p95": "Peak RSS P95", + "cpu_mean": "CPU mean", + "tie": "Tie", + "lowest_peak_rss_p50_lowest_mean_cpu_per_call_resource_minima_and_": "Lowest peak RSS P50: {value0}; lowest mean CPU per call: {value1}. Resource minima and task success are evaluated separately.", + "peak_rss_p50_change": "Peak RSS P50 change", + "cpu_mean_change": "CPU mean change", + "moli_1_1_9_with_the_same_layout_setting": "Moli 1.1.9 with the same layout setting", + "resources_are_measured_separately_from_success_on_372_cases_each_": "Resources are measured separately from success on 372 cases: each configuration fixes layout and runs 557 tasks five times. P50 is the median and P95 the 95th percentile; every metric in a scope includes the same passing and failing calls. Change baseline: {value0}. macOS measures process-tree RSS; Linux PSS is shown separately.", + "patched_resource_evidence": "Patched resource evidence", + "1_1_9_resource_evidence": "1.1.9 resource evidence", + "matched_resource_call_matrix": "Matched resource call matrix", + "serial_comparison_and_build_evidence": "Serial comparison and build evidence", + "moli_1_1_9_resource_statistics": "Moli 1.1.9 resource statistics", + "all_four_release_configurations_share_one_host_and_retain_separat": "All four release configurations share one host and retain separate browser processes and samplers. Task calls run serially in rotating order; each measurement covers only its own process tree. CPU is user plus system time.", + "peak_pss_mean": "Peak PSS mean", + "peak_pss_p50": "Peak PSS P50", + "peak_pss_p95": "Peak PSS P95", + "lowest_peak_pss_p50_lowest_mean_cpu_per_call_resource_consumption": "Lowest peak PSS P50: {value0}; lowest mean CPU per call: {value1}. Resource consumption and success are compared separately.", + "pss_p50_change": "PSS P50 change", + "mean_cpu_change": "Mean CPU change", + "1_1_4_1_1_8_resource_evidence": "1.1.4 / 1.1.8 resource evidence", + "1_1_9_measurements_and_calibration": "1.1.9 measurements and calibration", + "resource_statistics_for_seven_configurations": "Resource statistics for seven configurations", + "all_metrics_include_the_same_passing_and_failing_calls_p50_is_the": "All metrics include the same passing and failing calls. P50 is the median and P95 the 95th percentile. Every change uses 1.1.4 with layout off as its baseline.", + "resource_measurement_and_comparability": "Resource measurement and comparability", + "cpu_metric": "CPU metric", + "mean_per_call_cpu": "Mean per-call CPU", + "total_cpu_time": "Total CPU time", + "per_call_cpu_p50": "Per-call CPU P50", + "per_call_cpu_p95": "Per-call CPU P95", + "memory_metric": "Memory metric", + "mean_observed_pss_maximum": "Mean observed PSS maximum", + "observed_pss_maximum_p50": "Observed PSS maximum P50", + "observed_pss_maximum_p95": "Observed PSS maximum P95", + "pss_increase_p50": "PSS increase P50", + "fixture_application_traffic": "Fixture application traffic", + "total_downlink": "Total downlink", + "downlink_p50": "Downlink P50", + "total_uplink": "Total uplink", + "uplink_p50": "Uplink P50", + "cold_start_metric_off_on": "Cold-start metric · off {value0} / on {value1}", + "ready_time_p50": "Ready time P50", + "launch_cpu_p50": "Launch CPU P50", + "launch_observed_pss_p50": "Launch observed PSS P50", + "tie_f4719e": "tie", + "observed_profiled_resource_values": "Observed profiled resource values", + "all_call_cpu_observed_pss_maximum_p50_fixture_downlink_these_are_": "All-call CPU: {value0}; observed PSS maximum P50: {value1}; fixture downlink: {value2}. These are profiled observations; primary duration uses unprofiled calls.", + "cpu_and_computation": "CPU and computation", + "memory": "Memory", + "network_traffic": "Network traffic", + "cold_start": "Cold start", + "tasks_used_different_definition_versions_across_configurations_th": "{value0} tasks used different definition versions across configurations; the other {value1} used identical definitions. Scores retain each configuration's measured task version.", + "inspect_resource_statistics_and_measurement_scope": "Inspect resource statistics and measurement scope", + "explore_the_results": "Explore the results", + "compare_gained_passes_and_remaining_failures": "Compare gained passes and remaining failures", + "accepted_call_duration": "Accepted-call duration", + "mean_accepted_call_duration": "Mean accepted-call duration", + "accepted_call_p50_duration": "Accepted-call P50 duration", + "accepted_call_p95_duration": "Accepted-call P95 duration", + "tasks": "tasks", + "family": "Family", + "layout_on_pass_share": "Layout-on pass share", + "find_and_filter": "Find and filter", + "selection_rule": "Selection rule", + "subset": "Subset", + "attempt": "Attempt", + "off_status": "Off status", + "on_status": "On status", + "off_duration": "Off duration", + "on_duration": "On duration", + "off_cpu": "Off CPU", + "on_cpu": "On CPU", + "off_ending_pss": "Off ending PSS", + "on_ending_pss": "On ending PSS", + "task_id": "Task ID", + "task_sha_256": "Task SHA-256", + "features": "Features", + "case_verdict": "Case verdict", + "same_task_results": "Same-task results", + "paired_attempts": "Paired attempts", + "duration_comes_from_the_unprofiled_run_cpu_and_ending_pss_come_fr": "Duration comes from the unprofiled run; CPU and ending PSS come from the matching profiled call. Open a status to inspect that exact call's evidence.", + "driver_steps": "Driver steps", + "acceptance_contract": "Acceptance contract", + "scene": "Scene", + "grader": "Grader", + "unprofiled": "Unprofiled", + "profiled": "Profiled", + "verdict_and_response": "Verdict and response", + "profiled_verdict": "Profiled verdict", + "answer": "Answer", + "moli_resources": "Moli resources", + "observed_pss_maximum": "Observed PSS maximum", + "samples": "Samples", + "attempts_passed_all_must_pass": "{value0}/{value1} attempts passed; all must pass", + "total_for_calls": "Total for {value0} calls", + "compared_with_the_other_mode": "Compared with the other mode", + "failure_locator": "Failure locator", + "all_calls_passed_in_this_mode": "All {value0} calls passed in this mode.", + "result_summary": "Result summary", + "run_identities": "Run identities", + "unprofiled_run": "Unprofiled run", + "profiled_run": "Profiled run", + "calls_and_evidence": "{value0} calls and evidence", + "each_call_pairs_the_unprofiled_verdict_and_duration_with_the_prof": "Each call pairs the unprofiled verdict and duration with the profiled resource observation. Open evidence for exact grader, output, log or CDP data.", + "other_mode": "Other mode", + "fixed_tasks_fixed_layout_and_a_separate_resource_experiment": "Fixed tasks, fixed layout and a separate resource experiment.", + "evaluation_protocol": "Evaluation protocol", + "freeze_tasks_binaries_and_acceptance_rules": "Freeze tasks, binaries and acceptance rules", + "fix_version_and_layout_configuration": "Fix version and layout configuration", + "functional_372_cases_three_attempts_each": "Functional: 372 cases, three attempts each", + "resources_557_tasks_five_calls_each": "Resources: 557 tasks, five calls each", + "a_case_passes_only_when_all_three_attempts_pass": "A case passes only when all three attempts pass", + "all_call_statistics_and_fixed_209_task_chart_sample": "All-call statistics and fixed 209-task chart sample", + "configuration_table_and_chart": "Configuration table and chart", + "functional_scoring_and_baseline": "Functional scoring and baseline", + "all_configurations_use_the_same_372_case_denominator_a_case_passe": "All configurations use the same 372-case denominator. A case passes only when execution and acceptance checks pass in all three attempts. Both 1.1.8 layouts, both patched 1.1.9 layouts and both 1.1.9 layouts directly cover all 372 cases.", + "layout_off_1_1_4_is_the_baseline_the_sole_candidate_passed_by_lay": "Layout-off 1.1.4 is the baseline: the sole candidate passed by layout-off 1.1.8 failed all three direct checks; the other 371 retain the shared-failure relation. Its 0/372 consists of one directly checked case and 371 inherited outcomes, not a complete 1.1.4 rerun. Because baseline success is zero, success changes are expressed in percentage points.", + "task_selection_and_scope": "Task selection and scope", + "the_372_cases_are_all_cases_from_the_1_928_case_suite_that_moli_0": "The 372 cases are all cases from the 1,928-case suite that Moli 0.1.1 did not pass in all three attempts, spanning 15 driver types. Chrome outcomes do not affect selection. The other 1,556 cases are outside this evaluation, so these rates do not describe the full suite.", + "resources_use_a_separate_557_task_experiment_with_five_calls_per_": "Resources use a separate 557-task experiment with five calls per task, or 2,785 calls per configuration. The chart and overview table use 1,045 calls on 209 tasks fixed before execution; detailed resource tables also show all 557 tasks. P50 is the median and P95 the 95th percentile.", + "moli_1_1_4_1_1_8_run_in_docker_linux_containers_launched_from_the": "Moli 1.1.4 / 1.1.8 run in Docker Linux containers launched from the Mac, using Linux ARM64 binaries and PSS sampling. Moli 1.1.9 runs natively on macOS with Mach-O ARM64 binaries and RSS sampling. All seven configurations are reruns in this series, not the website’s 71 MiB result. Sharing a host does not imply an identical execution environment. CPU is process-tree user plus system time. Cross-platform memory and CPU changes relative to 1.1.4 are not calculated; same-platform percentages describe the measured sample. All four macOS resource configurations use release builds and one frozen call matrix, executed serially with rotating order.", + "rss_and_pss_memory_definitions": "RSS and PSS: memory definitions", + "both_use_mib_but_account_for_shared_memory_differently_and_cannot": "Both use MiB, but account for shared memory differently and cannot be interchanged or converted by a fixed ratio.", + "property": "Property", + "what_is_counted": "What is counted", + "pages_currently_resident_in_physical_memory_for_the_process": "Pages currently resident in physical memory for the process", + "private_resident_pages_plus_a_proportional_share_of_shared_reside": "Private resident pages plus a proportional share of shared resident pages", + "shared_pages": "Shared pages", + "counted_for_each_mapping_process_process_tree_sums_can_count_shar": "Counted for each mapping process; process-tree sums can count shared pages multiple times", + "counted_proportionally_reducing_double_counting_across_processes": "Counted proportionally, reducing double counting across processes", + "example": "Example", + "20_mib_private_plus_10_mib_shared_equally_with_one_other_process_": "20 MiB private plus 10 MiB shared equally with one other process: RSS = 30 MiB", + "same_example_pss_20_10_2_25_mib": "Same example: PSS = 20 + 10 / 2 = 25 MiB", + "collection_in_this_report": "Collection in this report", + "this_report_consistently_compares_pss": "This report consistently compares PSS", + "macos_ps_rss_field_summed_over_the_process_tree": "macOS: ps rss field, summed over the process tree", + "linux_pss_in_proc_pid_smaps_rollup_summed_over_the_process_tree": "Linux: Pss in /proc/PID/smaps_rollup, summed over the process tree", + "comparison_conditions": "Comparison conditions", + "matching_execution_environment_sampling_and_task_scope_cpu_compar": "Matching execution environment, sampling and task scope; CPU comparisons also require consistent conditions", + "matching_conditions_and_shared_process_environment_pss_is_not_rss": "Matching conditions and shared-process environment; PSS is not RSS", + "linux_kernel_rss_and_pss_definitions": "Linux kernel: RSS and PSS definitions", + "apple_resident_memory_and_shared_mappings": "Apple: resident memory and shared mappings", + "execution_and_evidence": "Execution and evidence", + "the_resource_experiment_executes_one_task_call_globally_at_a_time": "The resource experiment executes one task call globally at a time. Functional and resource measurements bind their build identities separately. Success applies to the corresponding functional build. Task definitions, program verdicts, stdout and error logs are available in task details. No independent per-call human review was performed.", + "where_the_time_reduction_came_from": "Where the time reduction came from", + "these_three_tasks_account_for_of_the_net_duration_change_positive": "These three tasks account for {value0}% of the net duration change. Positive differences mean lower duration with layout; negative values mean higher duration. CPU differences use profiled calls and duration differences use unprofiled calls.", + "call_duration": "{value0}-call duration", + "time_reduction": "Time reduction", + "cpu_reduction": "CPU reduction", + "same_patched_build_enabling_layout": "Same patched build: enabling layout", + "layout_on_patched_vs_1_1_9": "Layout on: patched vs. 1.1.9", + "1_1_9_enabling_layout": "1.1.9: enabling layout", + "layout_changes_and_version_changes": "Layout changes and version changes", + "compare_layout_off_on_within_the_patched_build_to_measure_layout_": "Compare layout off/on within the patched build to measure layout changes. Compare 1.1.9 and patched 1.1.9 with layout on to locate version differences. Gains and losses are both retained.", + "comparison": "Comparison", + "gained": "Gained", + "lost": "Lost", + "both_pass": "Both pass", + "both_fail": "Both fail", + "outcome_relation": "Outcome relation", + "passed_attempts": "Passed attempts", + "task_requirements_for_this_comparison": "Task requirements for this comparison", + "inspect_frozen_task_definition": "Inspect frozen task definition", + "same_task_version_comparison": "Same-task version comparison", + "view_1_1_8_task_requirements_and_results": "View 1.1.8 task requirements and results", + "moli_1_1_8_case_outcomes": "Moli 1.1.8 case outcomes", + "cases_gained": "Cases gained", + "cases_lost": "Cases lost", + "failure_evidence": "Failure evidence", + "open_the_failure_filter_for_exact_program_errors_and_full_logs": "Open the failure filter for exact program errors and full logs.", + "by_task_family": "By task family", + "pass": "Pass", + "fail": "Fail", + "1_1_9_resource_measurement_quality": "1.1.9 resource measurement quality", + "the_official_procedure_pairs_profiler_off_calibration_with_profil": "The official procedure pairs profiler-off calibration with profiler-on measurement and checks task coverage, build identity, PSS/CPU availability, observer effect and host load.", + "configuration": "Configuration", + "pss_coverage": "PSS coverage", + "cpu_coverage": "CPU coverage", + "profiler_calibration": "Profiler calibration", + "host_check": "Host check", + "failed": "Failed", + "moli_1_1_9_resource_evidence_quality": "Moli 1.1.9 resource evidence quality", + "each_fixed_configuration_has_one_measured_run_checks_cover_the_co": "Each fixed configuration has one measured run. Checks cover the complete task matrix, binary identity, resource coverage, and host observations. Profiler overhead was not separately measured.", + "rss_coverage": "RSS coverage", + "verify_evaluation_coverage_binary_identities_and_evidence_sources": "Verify evaluation coverage, binary identities and evidence sources.", + "functional_evidence_coverage": "Functional evidence coverage", + "direct_cases_5505c5": "Direct cases", + "inherited_cases": "Inherited cases", + "scoring_and_review_coverage": "Scoring and review coverage", + "outcomes_come_from_programmatic_evaluation_against_frozen_accepta": "Outcomes come from programmatic evaluation against frozen acceptance rules. Report checks cover call matrices, binary and task hashes, resource sample counts, aggregates and evidence integrity. No independent per-call human review was performed; program passes are not human-review conclusions.", + "download_the_data_model_and_evidence_index": "Download the data model and evidence index", + "pass_and_failure_changes_between_configurations": "Pass and failure changes between configurations", + "inspect_gained_passes_and_failures": "Inspect gained passes and failures", + "what_a_passed_case_means": "What a passed case means", + "each_task_defines_fixed_actions_and_acceptance_conditions_after_a": "Each task defines fixed actions and acceptance conditions. After a successful tool response, the evaluator checks whether the task requirements were met.", + "inspect_requirements_actual_output_and_evaluator_evidence": "Inspect requirements, actual output and evaluator evidence", + "scope_and_evidence": "Scope and evidence", + "reproduce_1_1_9_linux_release_resources": "Reproduce 1.1.9 Linux release resources", + "a_fixed_docker_linux_image_on_the_mac_host_557_tasks_five_attempt": "A fixed Docker Linux image on the Mac host; 557 tasks, five attempts each, jobs=1 and a 250 ms PSS interval. Tasks, dependencies and binary hashes are frozen before execution. Configurations run serially in independent containers.", + "measurement_id": "Measurement ID", + "resource_binary_sha_256": "Resource binary SHA-256", + "resource_measurement_script": "Resource measurement script", + "inspect_the_resource_measurement_script": "Inspect the resource measurement script", + "moli_1_1_9_resource_run_identities": "Moli 1.1.9 resource run identities", + "each_fixed_configuration_runs_557_tasks_5_calls_with_the_same_tas": "Each fixed configuration runs 557 tasks × 5 calls with the same tasks, seed and sampling protocol.", + "run_id": "Run ID", + "coverage": "Coverage", + "manifest_sha_256_prefix": "Manifest SHA-256 prefix", + "binary_sha_256": "Binary SHA-256", + "inspect_the_script_used_for_these_resource_measurements": "Inspect the script used for these resource measurements", + "run_identities_0df143": "Run identities", + "coverage_c8f31d": "Coverage", + "manifest_hash_prefix": "Manifest hash prefix", + "reproduction_controls": "Reproduction controls", + "use_official_moli_1_1_8_linux_aarch64_verify_sha_256_and_freeze_t": "Use official Moli 1.1.8 Linux aarch64, verify SHA-256, and freeze tasks, seed, one-worker order and Docker image. Run each mode unprofiled and with 500 ms profiling, three attempts per case per round, then compare verdicts.", + "host": "Host", + "linux_vm": "Linux VM", + "container_image_sha_256": "Container image SHA-256", + "task_manifest_sha_256": "Task manifest SHA-256", + "runner_source_sha_256": "Runner source SHA-256", + "fixture_tree_sha_256": "Fixture tree SHA-256", + "seed": "Seed", + "calls_per_task_and_concurrency": "Calls per task and concurrency", + "download_frozen_cohort": "Download frozen cohort", + "download_reproduction_script": "Download reproduction script", + "download_report_data_model": "Download report data model", + "toggle_navigation": "Toggle navigation", + "report_navigation": "Report navigation", + "language_label": "中文", + "skip_to_content": "Skip to content", + "toggle_theme": "Toggle theme", + "versions_and_layout": "/ Versions and layout", + "compatibility_and_resource_study": "{value0} · compatibility and resource study", + "method_evidence": "Method & evidence", + "failure_diagnosis": "Failure diagnosis", + "program_classification": "Program classification", + "independently_reviewed_cause": "Independently reviewed cause", + "full_result_and_exact_attempt": "Full result and exact attempt", + "find_in_evidence": "Find in evidence", + "no_match": "No match", + "first_match_located": "First match located", + "summary_chart": "{value0} fixed configurations, aligned with the summary table; baseline: {value1}", + "outcome_change_summary": "{value0} cases gained, {value1} lost; net change {value2} cases." + }, + "resource_methodology": [ + "Resources use a separate 557-task experiment with five attempts per task, or 2,785 calls per configuration. The chart and overview table use 1,045 calls on 209 tasks fixed before execution; the detailed resource tables also show all 557 tasks. Each scope retains passing and failing calls.", + "All seven configurations are measured in Docker Linux on the Mac host, using Linux ARM64 release binaries and the same resource collection method. PSS is read from /proc/PID/smaps_rollup and summed over the process tree; CPU is the tree's user plus system CPU time. Sampling uses a 250 ms interval and jobs=1, with configurations executed serially.", + "The official procedure runs profiler-off calibration followed by profiler-on measurement. Reported resources come from the latter; calibration checks observer effect. P50 is the median and P95 the 95th percentile. Every resource change uses 1.1.4 with layout off as the baseline and describes the corresponding sample statistic. The website's 71 MiB value is excluded from these seven configurations." + ] + } +} diff --git a/scripts/moli_report/ui_check.js b/scripts/moli_report/ui_check.js index 1b2cfdf45..2d84aa4f3 100644 --- a/scripts/moli_report/ui_check.js +++ b/scripts/moli_report/ui_check.js @@ -232,6 +232,9 @@ async function run() { throw Error("home summary count"); } if (route === "analysis") { + const labels = await page.locator(".filters label").allTextContents(); + if (labels.some((text) => /\bundefined\b|\bNaN\b|\[object Object\]/.test(text))) + throw Error("unresolved comparison filter label"); let actual = await page .locator(".metric-card .value") .allTextContents(); diff --git a/scripts/promote_webarena_trajectories.py b/scripts/promote_webarena_trajectories.py index 396ec1ecf..635df112f 100644 --- a/scripts/promote_webarena_trajectories.py +++ b/scripts/promote_webarena_trajectories.py @@ -9,11 +9,11 @@ from harness.trajectory_replay.webarena_dataset import build_verified_trajectory_dataset -CURRENT_PROFILE = "evaluation/trajectory-replay/webarena-verified/current-run.json" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() + parser.add_argument("--profile", required=True) parser.add_argument("--seeds", required=True) parser.add_argument("--receipt", action="append", required=True) parser.add_argument("--output", required=True) @@ -25,7 +25,7 @@ def main() -> None: data = build_verified_trajectory_dataset( args.seeds, args.receipt, - profile_path=CURRENT_PROFILE, + profile_path=args.profile, ) output = Path(args.output) output.parent.mkdir(parents=True, exist_ok=True) diff --git a/scripts/run_webarena_trajectory_replay.py b/scripts/run_webarena_trajectory_replay.py index dd90fa44c..d4d81c849 100644 --- a/scripts/run_webarena_trajectory_replay.py +++ b/scripts/run_webarena_trajectory_replay.py @@ -7,11 +7,11 @@ from harness.trajectory_replay.webarena_replay import run_replay_suite -CURRENT_PROFILE = "evaluation/trajectory-replay/webarena-verified/current-run.json" def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() + parser.add_argument("--profile", required=True) parser.add_argument("--seeds", required=True) parser.add_argument("--output", required=True) parser.add_argument("--cowagent-repo", required=True) @@ -58,7 +58,7 @@ def parse_args() -> argparse.Namespace: def main() -> None: args = parse_args() run_replay_suite( - profile_path=CURRENT_PROFILE, + profile_path=args.profile, seed_manifest_path=args.seeds, output_dir=args.output, cowagent_repo=args.cowagent_repo, diff --git a/tests/browser_use/test_report.py b/tests/browser_use/test_report.py index 87cc58ef0..05115ba78 100644 --- a/tests/browser_use/test_report.py +++ b/tests/browser_use/test_report.py @@ -428,53 +428,22 @@ def test_fix_log_missing_source_diff_is_explicit_and_binary_bound( report_module._load_fix_log(profile) -def test_cross_track_control_localizes_mechanism_evidence_and_keeps_links() -> None: +def test_cross_track_control_localizes_measured_contract_counts_and_evidence() -> None: bridge = json.loads(Path("research/browser-use-trajectory-replay-bridge.json").read_text()) report_module._validate_replay_contract_definitions(bridge) attempts = [ {"model": model, "engine": engine, "task_id": task_id, "score": 0} - for model in report_module.MODEL_LABELS - for engine in report_module.ENGINE_LABELS - for tasks in bridge["original_contract_task_groups"].values() - for task_id in tasks + for model in report_module.MODEL_LABELS for engine in report_module.ENGINE_LABELS + for tasks in bridge["original_contract_task_groups"].values() for task_id in tasks ] chinese = report_module._trajectory_replay_control(bridge, attempts) english = localize_html(chinese, PurePosixPath("analysis/index.html")) - assert "第 9 个决策" in chinese - assert "第 40 个决策" in chinese - assert "decision 9" in english - assert "Decision 40" in english - assert "reasoning_effort=low" in chinese and "reasoning_effort=low" in english - assert "81 条保留官方原合同,9 条使用修订合同" in chinese - assert "81 retain the original official contract and 9 use revised contracts" in english - assert "同一官方 evaluator" not in chinese - assert "same official evaluator" not in english - assert "591" in chinese and "228" in chinese - assert "notification_area" in english and "Referer" in english + assert "recorded_response" in english and "HAR" in english + assert "591" in chinese + assert "228" in chinese assert "0/81" in english and "0/59" in english and "0/22" in english assert "post-hoc diagnostic" in english - assert "固定动作、预录回答与绑定合同的重放验收结果" in chinese - assert ( - "Replay acceptance for fixed actions, prerecorded responses, and bound contracts" in english - ) - assert "recorded_response" in chinese and "recorded_response" in english - assert "不证明当前浏览器读到了所需数据" in chinese - assert "does not prove that the current browser read the required data" in english - assert "也不证明 Agent 在本次运行中计算出了答案" in chinese - assert "or that the Agent computed the answer during this run" in english - assert "要求网络事件的合同仍会检查本次 HAR" in chinese - assert "Contracts requiring network events still check the current HAR" in english - assert "给定可行路径后的执行能力" not in chinese - assert "measures execution after a viable path is given" not in english - assert "新发现的视口设置缺陷要求重新核对这一归因" in chinese - assert "viewport configuration defect requires that attribution to be reassessed" in english - assert "Task 173" in english and "widths of 992 or more" in english - assert "37 道检索题均只使用回答验收" in chinese - assert "All 37 retrieval tasks" in english - assert "the other 53 tasks cannot replace the original 90 as a new ranking" in english - for result_id in ("gpt-6-astra-chromium-124", "gpt-5.6-sol-moli-510"): - assert f"results/{result_id}.html#trajectory" in chinese - assert f"results/{result_id}.html#trajectory" in english + assert 'evidence-viewer.html?src=data/trajectory-replay-bridge.json' in english assert not any("\u4e00" <= char <= "\u9fff" for char in english) @@ -598,7 +567,15 @@ def test_replay_bridge_checks_trajectory_profile_hash(tmp_path: Path) -> None: def test_replay_bridge_accepts_current_bound_profile() -> None: - profile = json.loads(Path("evaluation/browser-use/current-report.json").read_text()) + bridge_source = Path("research/browser-use-trajectory-replay-bridge.json") + bridge_data = json.loads(bridge_source.read_text()) + profile = { + "selection_sha256": bridge_data["selection_sha256"], + "trajectory_replay_bridge": { + "source_path": str(bridge_source.resolve()), + "source_sha256": report_module._sha256(bridge_source), + }, + } bridge = report_module._load_trajectory_replay_bridge( profile, selection_sha256=profile["selection_sha256"] ) @@ -961,21 +938,27 @@ def _use_single_model_profile(monkeypatch) -> None: real_read_json = report_module._read_json def read_json(path: Path) -> dict: - value = real_read_json(path) - if path == Path("evaluation/browser-use/current-report.json"): + if path == Path("fixture-profile.json"): + value = { + "engines": ["chromium", "moli"], + "engine_labels": {"chromium": "Chromium", "moli": "Moli 1.1.3"}, + "baseline_engine": "chromium", + "models": ["gpt-5.6-sol"], + "model_labels": {"gpt-5.6-sol": "GPT-5.6 Sol"}, + } return { **value, "models": ["gpt-5.6-sol"], "model_labels": {"gpt-5.6-sol": "GPT-5.6 Sol"}, "trajectory_replay_bridge": {}, } - return value + return real_read_json(path) monkeypatch.setattr(report_module, "_read_json", read_json) monkeypatch.setattr( report_module, "load_failure_cause_review", - lambda: { + lambda *_args: { "schema": "lex_browser_use_layered_failure_cause_review/2", "cohorts": {}, }, @@ -1067,6 +1050,7 @@ def test_report_publishes_one_final_logical_result_and_aggregates_cost( evidence=evidence, output=output, selection_path=Path("evaluation/browser-use/webarena-verified-pilot.json"), + profile_path=Path("fixture-profile.json"), language=language, ) if language is not None: @@ -1321,10 +1305,12 @@ def test_report_renders_measured_runtime_identities(tmp_path: Path, monkeypatch) { "protocol": { "cowagent_commit": "abc123", + "max_task_executions": 3, "model": "gpt-5.6-sol", "reasoning_effort": "low", }, "environment": { + "platform": "Linux fixture host", "site_access_host": "192.168.5.2", "docker_architecture": "aarch64", "docker_server_version": "28.0", @@ -1356,6 +1342,7 @@ def test_report_renders_measured_runtime_identities(tmp_path: Path, monkeypatch) evidence=evidence, output=output, selection_path=Path("evaluation/browser-use/webarena-verified-pilot.json"), + profile_path=Path("fixture-profile.json"), ) text = (output / "reproduce/index.html").read_text(encoding="utf-8") @@ -1375,22 +1362,33 @@ def test_report_renders_measured_runtime_identities(tmp_path: Path, monkeypatch) assert "90 题 Browser Use 完整结果" in home_text assert "90 题核心指标" in home_text assert "首次成功与重试恢复" not in home_text - assert "Chromium 780×437/493、Moli 1920×1080" in home_text - assert "不是统一视口下的资源效率结论" in home_text - assert "共同成功子集也保留了历史视口差异" in home_text + assert "Chromium 780×437/493、Moli 1920×1080" not in home_text + assert "不能视为统一视口下的资源效率结论" in home_text + assert "共同成功子集仍保留各配置记录的运行环境" in home_text english_home = localize_html(home_text, PurePosixPath("index.html")) - assert "not resource efficiency at a common viewport" in english_home - assert "common-success subset also retains the historical viewport differences" in english_home + assert "resource efficiency at a common viewport" in english_home + assert "common-success subset retains each configuration" in english_home methodology_text = (output / "methodology/index.html").read_text(encoding="utf-8") assert "webarena-autonomous-evaluation-flow.png" in methodology_text - assert "ContainerManager.start(reset=True)" in methodology_text + assert "环境准备回执" in methodology_text assert "192.168.5.2" in methodology_text assert "不含点击序列" in methodology_text + for page in (methodology_text, text): + assert "Colima" not in page + assert "macOS" not in page + assert "Linux fixture host" in text + assert "官方数据 812 题" not in methodology_text + assert "663" not in methodology_text assert "计划与实际对照" in methodology_text - assert "不能视为浏览器是唯一变量的实验" in methodology_text + assert "每题执行上限:3" in methodology_text + assert "冻结配置及实际执行记录" in methodology_text + assert "下一轮轨迹重放" not in methodology_text + assert "678 个候选" not in methodology_text + assert "Mac 上可以继续扩展哪些实例" not in methodology_text assert "只替换浏览器" not in methodology_text english_methodology = localize_html(methodology_text, PurePosixPath("methodology/index.html")) - assert "the browser was not the only experimental variable" in english_methodology + assert "frozen configuration and recorded executions" in english_methodology + assert "execution limit per task: 3" in english_methodology for name in ( "changes/index.html", "datasets/index.html", @@ -1487,7 +1485,7 @@ def test_joint_report_builds_two_model_by_two_browser_matrix(tmp_path: Path, mon real_read_json = report_module._read_json def read_json(path: Path) -> dict: - if path == Path("evaluation/browser-use/current-report.json"): + if path == Path("fixture-profile.json"): return { "engines": ["chromium", "moli"], "engine_labels": {"chromium": "Chromium", "moli": "Moli"}, @@ -1501,7 +1499,7 @@ def read_json(path: Path) -> dict: monkeypatch.setattr( report_module, "load_failure_cause_review", - lambda: { + lambda *_args: { "schema": "lex_browser_use_layered_failure_cause_review/2", "cohorts": {}, }, @@ -1512,10 +1510,15 @@ def read_json(path: Path) -> dict: evidence=[model_a, model_b], output=output, selection_path=Path("evaluation/browser-use/webarena-verified-pilot.json"), + profile_path=Path("fixture-profile.json"), ) data = json.loads((output / "report-data.json").read_text(encoding="utf-8")) assert set(data["summary"]) == {"model-a", "model-b"} + # A new instance must not inherit another report's release or review claims. + assert json.loads((output / "report-changes.json").read_text()) == {} + assert "2026-09-" not in (output / "changes/index.html").read_text() + assert data["summary"]["model-a"]["chromium"]["completed"] == 1 assert data["summary"]["model-b"]["moli"]["completed"] == 1 assert len(list((output / "results").glob("*.html"))) == 4 @@ -1576,3 +1579,43 @@ def test_pairing_uses_dynamic_baseline_for_three_browsers(monkeypatch) -> None: assert comparisons["first"]["baseline_engine"] == "baseline" assert comparisons["first"]["comparison_only"] == 1 assert comparisons["third"]["comparison_only"] == 2 + + +def test_replay_control_uses_selected_counts_and_origin_inventory(): + bridge = { + "effective_results": { + "chromium": {"tasks": 7, "passes": 5}, + "fixture": {"tasks": 7, "passes": 3}, + }, + "chromium_trajectory_provenance": { + "expert_repair_spec": 2, + "successful_browser_use_execution": 5, + "successful_browser_use_execution_by_origin": {"fixture-model_fixture": 5}, + }, + "contract_counts": {"official_task_contract": 5, "corrected_replay_contract": 2}, + "source_contract_counts": { + "authoritative_successful_browser_use_execution": { + "official_task_contract": 3, + "corrected_replay_contract": 2, + }, + "expert_repair_spec": {"official_task_contract": 2, "corrected_replay_contract": 0}, + }, + "correction_categories": { + "event_selection": [], + "event_selection_and_expected": [], + "reference_or_acceptance": [], + }, + } + page = report_module._trajectory_replay_control(bridge) + english = localize_html(page, PurePosixPath("analysis/index.html")) + assert "5/7" in page and "3/7" in page + bridge["effective_results"]["moli"] = {"tasks": 7, "passes": 4, "label": "Replay browser 9"} + from unittest.mock import patch + with patch.dict(report_module.ENGINE_LABELS, {"moli": "Unrelated autonomous browser 99"}): + isolated = report_module._trajectory_replay_control(bridge) + assert "Replay browser 9" in isolated + assert "Unrelated autonomous browser 99" not in isolated + assert "fixture-model_fixture" in page + assert "recorded_response" in english and "HAR" in english + for obsolete in ("90/90", "36", "37", "Task 173", "Task 676", "Colima"): + assert obsolete not in page diff --git a/tests/browser_use/test_report_check.py b/tests/browser_use/test_report_check.py index e4164686b..612e81d16 100644 --- a/tests/browser_use/test_report_check.py +++ b/tests/browser_use/test_report_check.py @@ -125,7 +125,7 @@ def test_complete_check_rejects_unbound_review_before_evidence(tmp_path): @pytest.mark.parametrize( - "relative", ["../outside.json", "/tmp/review.json", "research/linked.json"] + "relative", ["../outside.json", "research/linked.json"] ) def test_complete_check_rejects_escaping_or_linked_review(tmp_path, relative): root = tmp_path / "authority" @@ -166,3 +166,57 @@ def test_linked_authority_profile_is_rejected(tmp_path): require_complete=False, authority_root=root, ) + + +def test_external_profile_and_bound_review_do_not_use_default_authority(tmp_path, monkeypatch): + root = tmp_path / 'authority' + source = tmp_path / 'external-review.json' + source.write_text('{}') + profile = authority(root, failure_cause_review={ + 'required_for_publication': True, 'source_path': str(source), + 'source_sha256': hashlib.sha256(source.read_bytes()).hexdigest()}) + import harness.browser_use.report as renderer + for name in ('ENGINE_LABELS', 'MODEL_LABELS', 'BASELINE_ENGINE'): + monkeypatch.setattr(renderer, name, getattr(renderer, name)) + external = tmp_path / 'instance.json' + external.write_bytes(profile.read_bytes()) + (profile.parent / 'webarena-verified-pilot.json').write_text('{}') + profile.unlink() + def evidence_boundary(_paths): + raise LookupError('bound external inputs reached') + monkeypatch.setattr(checker, '_load_evidence_bundles', evidence_boundary) + with pytest.raises(LookupError, match='external inputs reached'): + checker.check(tmp_path / 'report', tmp_path / 'evidence', None, + require_complete=True, profile_path=external, authority_root=root) + source.write_text('{"changed":true}') + with pytest.raises(ValueError, match='bound to the failure-cause review'): + checker.check(tmp_path / 'report', tmp_path / 'evidence', None, + require_complete=True, profile_path=external, authority_root=root) + + +@pytest.mark.parametrize("defect", [None, "count", "host", "diagram"]) +def test_methodology_uses_selected_evidence_instead_of_historical_deployment(defect): + content = ( + '

Linux fixture · selected.gateway

' + '' + '
FixtureEvaluator2
' + '
new-site2
' + ) + tasks = [ + { + "sites": ["new-site"], + "contract": {"evaluation_contract": [{"evaluator": "FixtureEvaluator"}]}, + } + ] * 2 + environment = {"platform": "Linux fixture", "site_access_host": "selected.gateway"} + if defect == "count": + content = content.replace("2", "812", 1) + if defect == "host": + content = content.replace("selected.gateway", "historical.gateway") + if defect == "diagram": + content = content.replace('id="deployment-flow"', 'id="unrelated"') + if defect is None: + checker.check_methodology_evidence(content, tasks, environment) + else: + with pytest.raises(ValueError, match="methodology"): + checker.check_methodology_evidence(content, tasks, environment) diff --git a/tests/browser_use/test_selection.py b/tests/browser_use/test_selection.py index 7cc58205c..b118d26ca 100644 --- a/tests/browser_use/test_selection.py +++ b/tests/browser_use/test_selection.py @@ -202,3 +202,27 @@ def test_task_type_count_validation_reports_missing_response_evaluator() -> None SimpleNamespace(get_task=lambda _: task), enforce_contract_integrity=False, ) + + +def test_report_selection_uses_declared_scope_and_rejects_changed_bytes(tmp_path): + import hashlib + import json + + from harness.browser_use.selection import report_selection_path + + payload = json.loads(Path('evaluation/browser-use/webarena-verified-pilot.json').read_text()) + payload['tasks'] = [next(task for task in payload['tasks'] if task['tier'] == tier) + for tier in ('easy', 'hard')] + payload['metadata']['tier_counts'] = {'easy': 1, 'medium': 0, 'hard': 1} + path = tmp_path / 'selection.json' + path.write_text(json.dumps(payload)) + digest = hashlib.sha256(path.read_bytes()).hexdigest() + profile = {'selection_sha256': digest, 'task_selection': {'path': str(path), 'sha256': digest}} + assert len(load_pilot(path).tasks) == 2 + assert report_selection_path(profile) == path + payload['tasks'].pop() + path.write_text(json.dumps(payload)) + with pytest.raises(ValueError, match='instance profile'): + report_selection_path(profile) + with pytest.raises(ValueError, match='tier counts'): + load_pilot(path) diff --git a/tests/fixtures/native-route-baseline.json b/tests/fixtures/native-route-baseline.json index af4a2959c..a85b41f96 100644 --- a/tests/fixtures/native-route-baseline.json +++ b/tests/fixtures/native-route-baseline.json @@ -1,74 +1,74 @@ { - "moli_webfetch:defaults": { - "cells": 168, - "sha256": "4e66d03b097e2e0b8d8a3025079eb0a30bf565120199c97a410abc044e1bb027" + "cloudflare_content:configured": { + "cells": 192, + "sha256": "c501d817b59dbefc362964386736f67ae4c46a7f2981aebf84dae53f10ae677b" }, - "moli_webfetch:configured": { - "cells": 168, - "sha256": "f9586a678d144f518486e9e436dd5c64880f048b6e0e4ed93ba26beac38001c0" + "cloudflare_content:defaults": { + "cells": 192, + "sha256": "287739522a6a6c57e529380e909472a5d515a846a2979bd659a67222b0f9dfd6" }, - "firecrawl:defaults": { - "cells": 168, - "sha256": "85fe1b1eb0d29ddf7f293c91266e896c3616a7b2e839feb8a56b48beafb304c8" + "cloudflare_markdown:configured": { + "cells": 192, + "sha256": "4905efb447fcdb4aec57bc73dd26880bf1c0e9084a71fca5374e442416db60ae" }, - "firecrawl:configured": { - "cells": 168, - "sha256": "f2a97410b7f9e06ae06637d0e7656e138a3521a60dd8027c426614f30b639dea" + "cloudflare_markdown:defaults": { + "cells": 192, + "sha256": "2e9171663d9791377f107794667707836d65572ed9e06077d275b0e0a00c791f" }, - "tavily_extract:defaults": { - "cells": 168, - "sha256": "1da2806f37b9b2b224265e220a96fb9849ea82f0c40f3d7777ed1cdc3d0c15f6" + "cowagent_builtin:configured": { + "cells": 192, + "sha256": "709a83a64fc0b36e9ae446f3c430c42223627d7332c6853e4d30091a9ed0c992" }, - "tavily_extract:configured": { - "cells": 168, - "sha256": "a40b318ccca1978af7462a62baa0d4d47fbab326162f2c83446432e0b3c0b057" + "cowagent_builtin:defaults": { + "cells": 192, + "sha256": "f952cc5a5f0e59975b18403da67bbbc138d886febdabd817ccd2682a4fdef39b" + }, + "exa_contents:configured": { + "cells": 192, + "sha256": "671cdcdd4902ec034caee226164861e3503bac84c8ce9832ec8413896f0b16f8" }, "exa_contents:defaults": { - "cells": 168, - "sha256": "1312dfa3eb33b9b29de55543fc3ffd663b0e59f1becb2cd10a7e12777d31bca0" + "cells": 192, + "sha256": "d65bc8289d165286e6341568e7009233549cfb641bf6b6ad293a410675fb8c20" }, - "exa_contents:configured": { - "cells": 168, - "sha256": "9c7ffd79e20fcfb322a317dfae3271203c1c2c42aa09f52daaaabadfdf946d99" + "firecrawl:configured": { + "cells": 192, + "sha256": "7b3a41efd52dbe26075e1b7fb4525adba0ae2edf1bebb5c84ba6391e0392e079" }, - "cloudflare_markdown:defaults": { - "cells": 168, - "sha256": "bc8d5ea1939f7810a0d1b38d0ee98f86bbc8d53e9d25940dea9efcbadb0a7328" + "firecrawl:defaults": { + "cells": 192, + "sha256": "b8d94e892531dc7f5d3ea8a805a96bf556de609e02fb4d50abe8877723186330" }, - "cloudflare_markdown:configured": { - "cells": 168, - "sha256": "8fe63eb401e2964c6af30ec49dbc66625ccaf8e4f75af573e8f7f6c7563fed25" + "fixture:configured": { + "cells": 192, + "sha256": "c95ef4b78f9df35c12713d7dac41f1c3abc675925653f43a4a22ee09152489e2" }, - "cloudflare_content:defaults": { - "cells": 168, - "sha256": "a9c73f32a531965a9e55293c18b72766266010a231fcf9150af41922d4eeb8bf" + "fixture:defaults": { + "cells": 192, + "sha256": "0ec20a64f8c447a737c303c02eafdb218a94e3f4930680cc34ab9fa33d8e744b" }, - "cloudflare_content:configured": { - "cells": 168, - "sha256": "6e3983ae12da6d3f28edb35b102f48e13b51e31cf3faeeeb5297e41840158b75" + "moli_webfetch:configured": { + "cells": 192, + "sha256": "940e90b1de64c65270d1db7726eec53680c68ca03dc5e43193b5eff4628c6338" }, - "subprocess_jsonl:defaults": { - "cells": 168, - "sha256": "4edb323b311ee86004aa160120562ab42ca95bcb65da7b330a9e819e4148d28e" + "moli_webfetch:defaults": { + "cells": 192, + "sha256": "551cfed573d065b40f58f53e20e78e5c6a1d7b77fd3808c67e6400c785fe892a" }, "subprocess_jsonl:configured": { - "cells": 168, - "sha256": "6612065c03bd9061fd165345c96d239ec973493e190dea7bf829644412dda6a1" + "cells": 192, + "sha256": "7d03a82682e0ac27139633c071fe119841baebf889f224fc5eea7c745088fcd1" }, - "cowagent_builtin:defaults": { - "cells": 168, - "sha256": "bbbfed5ef4238db2652f8aaac170d5144ab68dbc962e53fc59300d51ff78c672" - }, - "cowagent_builtin:configured": { - "cells": 168, - "sha256": "f5a5763367ad7861c3f8e7d8180c27ecf5104829a96036e0610650fa24b851f2" + "subprocess_jsonl:defaults": { + "cells": 192, + "sha256": "c18e2cacbb00b1759156548a62b499fcd9c0dbcffd531fb3f454ef0a1f82900c" }, - "fixture:defaults": { - "cells": 168, - "sha256": "46897fa8480097bed7680f1e0057abaddd45cf8e6acc3a9857b80580ba526eae" + "tavily_extract:configured": { + "cells": 192, + "sha256": "aecbb4eacbdefc6a9726e97d30b263d4f94c39012d9829afbab8d936e0c22287" }, - "fixture:configured": { - "cells": 168, - "sha256": "78d72000b12c48bcafc5c1fbe6a704194ee09202f9307615f8e2336ad5486bfe" + "tavily_extract:defaults": { + "cells": 192, + "sha256": "84a39ad2141dca513b6d4c0e89293d8993facd3f47b449c8094321d171262930" } } diff --git a/tests/package_smoke.py b/tests/package_smoke.py index ea4846beb..51ad940e7 100644 --- a/tests/package_smoke.py +++ b/tests/package_smoke.py @@ -256,10 +256,11 @@ def verify_lexbench_package(root: Path, renderer: Path) -> None: for name in ('app.js', 'style.css', 'mermaid.min.js', 'mermaid-LICENSE.txt'): (source / name).write_text(name) (source / 'app.js').write_bytes(renderer.read_bytes()) + (source / 'messages.json').write_bytes(renderer.with_name('messages.json').read_bytes()) (source / 'evidence.bin').write_bytes(bytes(range(256))) destination = root / 'lexbench-package' summary = package(source, destination, pack_limit=80) - assert summary['bundled_files'] == 1 + assert summary['bundled_files'] == 2 catalog = json.loads((destination / 'bundle-manifest.json').read_text())['files'] entry = catalog['evidence.bin'] payload = gzip.decompress((destination / entry['pack']).read_bytes()) @@ -462,23 +463,18 @@ def verify_release_check_helpers(root: Path, source_root: Path) -> None: command = [sys.executable, str(source_root / 'scripts/check_wcxb_report.py')] subprocess.run([*command, '--help'], check=True, stdout=subprocess.DEVNULL) from harness.web_fetch.wcxb_publication import write_inventory - write_inventory(pages, json.loads((source_root / 'evaluation/wcxb/current-report.json').read_bytes())) - rejected = subprocess.run([*command, str(pages), '--authority-root', str(source_root)], + profile = root / "selected-profile.json" + profile.write_text(json.dumps({"comparison_kind": "wcxb_frozen_html_to_markdown"})) + (root / "report-content-binding.json").write_text("{}") + write_inventory(pages, json.loads(profile.read_bytes())) + rejected = subprocess.run([*command, str(pages), '--profile', str(profile)], capture_output=True, text=True) assert rejected.returncode != 0 and 'guide/index.html' in rejected.stderr, rejected.stderr - assert 'release authority is missing' not in rejected.stderr - for script, error in ( - ('check_webmainbench_report.py', 'dataset.json'), - ('check_webmainbench_full_report.py', 'Exactly one method-cohort binding'), - ): + for script in ('check_webmainbench_report.py', 'check_webmainbench_full_report.py'): command = [sys.executable, str(source_root / 'scripts' / script)] subprocess.run([*command, '--help'], check=True, stdout=subprocess.DEVNULL) unbound = subprocess.run([*command, str(pages)], capture_output=True, text=True) - assert unbound.returncode != 0 and 'release authority is missing' in unbound.stderr - rejected = subprocess.run([*command, str(pages), '--authority-root', str(source_root)], - capture_output=True, text=True) - assert rejected.returncode != 0 and error in rejected.stderr, rejected.stderr - assert 'release authority is missing' not in rejected.stderr + assert unbound.returncode != 0 and 'authority' in unbound.stderr, unbound.stderr def verify_environment_preparation(root: Path, source_root: Path) -> None: @@ -1503,8 +1499,8 @@ def synthetic_child(command, **kwargs): try: os.chdir(work) with patch.object(subprocess, 'run', synthetic_child): - certification.main(['--source', 'source.jsonl', '--scorer', 'scorer', '--output-root', 'run']) - certification.main_score(['--source', 'source.jsonl', '--scorer', 'scorer', '--cohort', 'run']) + certification.main(['--source', 'source.jsonl', '--scorer', 'scorer', '--profile', 'profile.json', '--output-root', 'run']) + certification.main_score(['--source', 'source.jsonl', '--scorer', 'scorer', '--profile', 'profile.json', '--cohort', 'run']) assert len(calls) == 4 assert (work / 'run/scoring-certificate.json').is_file() for module in ('webmainbench_runner', 'webmainbench_scoring', 'webmainbench_certification'): @@ -1713,7 +1709,7 @@ def main(): root / 'cohort', root / 'cohort/dataset.json', root_cause_category='answer-surface', root_cause='Synthetic installation control preserves the complete answer scoring surface.', - regression_ids=['answer-title-supplies-required-fact'], + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact'], ) assert receipt['provider_calls'] == 0 and receipt['task_decision_changes'] == [] revised = json.loads((root / 'cohort/results.jsonl').read_text()) diff --git a/tests/test_acceptance_scope.py b/tests/test_acceptance_scope.py new file mode 100644 index 000000000..1e99b2248 --- /dev/null +++ b/tests/test_acceptance_scope.py @@ -0,0 +1,43 @@ +"""Report coverage describes declarations, not inferred retrieval capabilities.""" +from copy import deepcopy +from harness.web_fetch.report_components import acceptance_scope + + +def test_scope_separates_literal_facts_body_and_records_without_changing_verdicts(): + cases = { + 'navigation': {'id': 'navigation', 'track': 'popular', 'expected_any': ['Home'], 'reference_answer': 'Instruction to read the page'}, + 'article': {'id': 'article', 'track': 'dynamic', 'page_identity_required': True, + 'ground_truth_main_content': 'Full independent article', 'required_facts': [{'id': 'fact'}], 'body_requirements': {'regions': ['body']}, + 'acceptance_contract': {'minimum_records': 3}}, + 'resource': {'id': 'resource', 'track': 'web', 'task_kind': 'resource', + 'expected_resources': [{'id': 'video'}]}, + } + rows = [ + {'provider': 'service', 'case_id': 'navigation', 'ok': True, 'metrics': {'content_pass': True}}, + {'provider': 'service', 'case_id': 'article', 'ok': True, + 'metrics': {'content_pass': True, 'forbidden_requirement_hits': [], 'page_identity_required': True, 'page_identity_status': 'verified', 'blocked_page': True}}, + {'provider': 'service', 'case_id': 'resource', 'ok': True, + 'metrics': {'resource_pass': True, 'resource_precision': .5, 'unexpected_resource_urls': ['other']}}, + ] + before = deepcopy((cases, rows)) + scope = acceptance_scope(cases, rows) + assert scope['families']['popular']['literal'] == 1 + assert scope['families']['popular']['body_regions'] == scope['families']['popular']['full_reference'] == 0 + assert scope['families']['dynamic']['full_reference'] == 1 + assert scope['families']['dynamic']['facts'] == scope['families']['dynamic']['body_regions'] == 1 + assert scope['families']['dynamic']['records'] == scope['families']['dynamic']['identity'] == 1 + method = scope['methods']['service'] + assert method['passes'] == 3 and method['unverified_identity_passes'] == 2 + assert method['blocked_diagnostics'] == method['resource_results'] == method['unexpected_resources'] == 1 + assert method['resource_precision_sum'] == .5 + assert (cases, rows) == before + + +def test_affiliation_is_exact_repository_scope_not_a_similar_name(): + cases = {name: {'id': name, 'url': url} for name, url in { + 'owned': 'https://github.com/lexmount/moli', + 'readme': 'https://github.com/lexmount/moli/blob/main/README.md', + 'other': 'https://github.com/lexmount/molina', + 'external': 'https://github.com/another/moli', + }.items()} + assert acceptance_scope(cases, [])['affiliated_cases'] == ['owned', 'readme'] diff --git a/tests/test_accounts.py b/tests/test_accounts.py index 03ada2114..593d2bf87 100644 --- a/tests/test_accounts.py +++ b/tests/test_accounts.py @@ -48,10 +48,11 @@ def test_report_uses_only_measured_methods_and_includes_all_hosted_accounts(): def test_unknown_method_has_explicit_missing_access(): - access = method_access('unknown-method', []) - assert access['type'] == 'unknown' - assert 'Not registered' in access['account'] - assert access['env_vars'] == [] + for language, label in (('zh', '未登记'), ('en', 'Not registered')): + access = method_access('unknown-method', [], language=language) + assert access['type'] == 'unknown' + assert access['account'] == label + assert access['env_vars'] == [] assert 'unknown-method' in render_account_requirements([{'provider': 'unknown-method'}]) diff --git a/tests/test_assembly_evidence.py b/tests/test_assembly_evidence.py index ec0f448f2..30f204c83 100644 --- a/tests/test_assembly_evidence.py +++ b/tests/test_assembly_evidence.py @@ -77,7 +77,7 @@ def test_canonical_dataset_promotes_an_explicit_full_review_policy(source, tmp_p (root / 'dataset.json').write_text(json.dumps(baseline)) revise(root, root / 'dataset.json', root_cause_category='answer-surface', root_cause='Synthetic retained-answer correction is checked on the complete cohort.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) baseline = json.loads((root / 'dataset.json').read_text()) canonical = tmp_path / 'canonical.json' promoted = json.loads(json.dumps(baseline)) @@ -721,7 +721,7 @@ def test_scored_cohort_composition_keeps_authority_without_starting_review(sourc root, _ = source revise(root, root / 'dataset.json', root_cause_category='answer-surface', root_cause='Synthetic retained-answer correction is checked on the complete cohort.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) assert valid_scoring_revision(root) original_bytes = (root / 'results.jsonl').read_bytes() original_history = (root / 'scoring-revisions.json').read_bytes() @@ -809,7 +809,7 @@ def test_scored_cohort_composition_rejects_invalid_authority_or_changed_verdict( (root / 'dataset.json').write_text(json.dumps(metadata)) revise(root, root / 'dataset.json', root_cause_category='answer-surface', root_cause='Synthetic retained-answer correction is checked on the complete cohort.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) dataset = json.loads((root / 'dataset.json').read_text()) case = EvalCase(**dataset['cases'][0]) class Fixture(Provider): diff --git a/tests/test_dataset_assembly.py b/tests/test_dataset_assembly.py index d545111c9..0efa5595d 100644 --- a/tests/test_dataset_assembly.py +++ b/tests/test_dataset_assembly.py @@ -320,3 +320,19 @@ def test_input_changing_case_correction_requires_all_method_rerun(tmp_path): assert dataset["cases"][0]["category"] == "current_homepage" assert dataset["cases"][0]["tags"] == ["current"] assert dataset["comparison"]["case_corrections"]["rerun_all_methods"] == ["plain"] + + +def test_explicit_external_profile_is_portable_and_preserves_input_hash_checks(tmp_path): + repository = tmp_path / 'checkout' + path, assembly = _fixture(repository) + profile = tmp_path / 'instance/profile.json' + _write(profile, json.loads((repository / 'profile.json').read_text())) + dataset, raw = assemble_profile_dataset(path, repository=repository, + profile_path_override=profile, require_output_hash=False) + assert dataset['comparison']['reproduction_profile'] == 'profile.json' + assert str(tmp_path).encode() not in raw + source = repository / 'plain.json' + source.write_text('{"cases": []}') + with pytest.raises(ValueError, match='source hash changed'): + assemble_profile_dataset(path, repository=repository, + profile_path_override=profile, require_output_hash=False) diff --git a/tests/test_external_local_report.py b/tests/test_external_local_report.py index 053decb22..14904a5a2 100644 --- a/tests/test_external_local_report.py +++ b/tests/test_external_local_report.py @@ -19,9 +19,16 @@ def write(path, value): return hashlib.sha256(path.read_bytes()).hexdigest() -@pytest.mark.parametrize("case_count,method_count,timeout", [(5, 2, 18), (12, 3, 30), (7, 4, 11)]) +@pytest.mark.parametrize("case_count,method_count,timeout,failures", [ + (1, 1, 7, True), + (1, 1, 7, False), + (5, 2, 18, True), + (12, 3, 30, True), + (7, 4, 45, True), + (7, 4, 11, True), +]) def test_external_matrix_through_build_and_check_clis(tmp_path, monkeypatch, - case_count, method_count, timeout): + case_count, method_count, timeout, failures): from harness.web_fetch.providers.fixture import FixtureProvider original_fetch = FixtureProvider.fetch @@ -32,6 +39,7 @@ def versioned_fixture(self, case, timeout, **kwargs): return result monkeypatch.setattr(FixtureProvider, "fetch", versioned_fixture) + root = Path(__file__).resolve().parents[1] evidence = tmp_path / "evidence" methods = [f"synthetic-{n}" for n in range(method_count)] @@ -52,9 +60,10 @@ def versioned_fixture(self, case, timeout, **kwargs): "source_reverified": False}} frozen = evidence / "local-evidence/input-dataset.json" dataset_digest = write(frozen, dataset) - config = {name: {"type": "fixture", "fail_cases": ["task-0"]} for name in methods} - # Keep one default-only method and one explicit public control. Private - # configuration fields must never be copied into the generated report. + config = {name: {"type": "fixture", "fail_cases": ["task-0"] if failures else []} + for name in methods} + # Keep a public control on the first method and defaults on additional methods. + # Private configuration fields must never enter the generated report. config[methods[0]].update(max_bytes=12345, api_key="synthetic-private-credential") config_path = evidence / "providers.json" config_digest = write(config_path, {"providers": config}) @@ -98,7 +107,7 @@ def versioned_fixture(self, case, timeout, **kwargs): assert manifest["generated_languages"] == ["zh", "en"] for name in methods: for prefix in ['', 'en/']: - page = (output / prefix / f'attempts/{name}__task-1__r001.html').read_text() + page = (output / prefix / f'attempts/{name}__task-{min(1,case_count-1)}__r001.html').read_text() assert name+' 9.9.9' in page assert 'Moli 原生 Markdown' not in page assert 'Moli native Markdown' not in page @@ -108,7 +117,8 @@ def versioned_fixture(self, case, timeout, **kwargs): for prefix, unit in (("", "秒"), ("en/", "seconds")): methodology = (output / prefix / "methodology/index.html").read_text() assert f"{timeout} {unit}" in methodology - assert "45 秒" not in methodology + if timeout != 45: + assert "45 秒" not in methodology assert "6.1 秒" not in methodology assert all(row["provider_metadata"]["reported_version"] == "9.9.9" for row in saved) if timeout == 30: @@ -128,7 +138,7 @@ def versioned_fixture(self, case, timeout, **kwargs): options = load_frozen_method_options(output, saved) assert options["schema_version"] == 2 assert options["providers"][methods[0]] == {"max_bytes": 12345} - assert options["providers"][methods[-1]] == {} + assert all(options["providers"][name] == {} for name in methods[1:]) assert options["result_count"] == len(saved) assert options["provider_config_sha256"] == config_digest projected = (output / options["source_config"]["path"]).read_bytes() diff --git a/tests/test_file_report_reason.py b/tests/test_file_report_reason.py index ba0f6a0b9..695640534 100644 --- a/tests/test_file_report_reason.py +++ b/tests/test_file_report_reason.py @@ -22,10 +22,10 @@ def test_missing_page_identity_is_not_a_file_or_uncalled_result(self): from harness.web_fetch.report import failure_category, result_label row={'ok':True,'metrics':{'request_ok':True,'page_identity_required':True, 'page_identity_status':'unavailable'}} - self.assertEqual(result_label(row,{}),'无法判定') + self.assertEqual(result_label(row,{}),'未通过') self.assertIn('最终网页身份',reason(row,{})) self.assertNotIn('原文件',reason(row,{})) - self.assertIn('无法判定',failure_category(row,{})) + self.assertIn('身份',failure_category(row,{})) def test_shared_evidence_lines_preserve_complete_exact_matches(self): from harness.web_fetch.report import _exact_evidence_lines, _normalized_evidence_lines @@ -43,13 +43,23 @@ def test_unknown_result_is_visible_but_not_in_acceptance_denominator(self): rows=[{'provider':'p','case_id':'a','ok':True,'elapsed_ms':1, 'metrics':{'content_pass':True}}, {'provider':'p','case_id':'b','ok':True,'elapsed_ms':1, - 'metrics':{'page_identity_required':True,'page_identity_status':'unavailable'}}] + 'metrics':{'rule_review_status':'unresolved'}}] rendered=overall_ranking(rows,cases) self.assertIn('无法判定',rendered) self.assertIn('1/1',rendered) self.assertIn('可判定数',rendered) self.assertNotIn('每题计一次通过或失败',rendered) + def test_required_identity_failure_stays_in_the_planned_denominator(self): + from harness.web_fetch.report import overall_ranking + cases = {key: {'id': key, 'track': 'web'} for key in ('a', 'b')} + rows = [{'provider': 'p', 'case_id': key, 'ok': True, 'elapsed_ms': 1, + 'metrics': ({'content_pass': True} if key == 'a' else + {'page_identity_required': True, 'page_identity_status': 'unavailable'})} + for key in cases] + self.assertIn('1/2', overall_ranking(rows, cases)) + + class BoundedAnchorEvidenceTests(unittest.TestCase): def test_single_line_html_does_not_repeat_the_answer_for_each_anchor(self): diff --git a/tests/test_file_scoring_evidence.py b/tests/test_file_scoring_evidence.py new file mode 100644 index 000000000..02a365424 --- /dev/null +++ b/tests/test_file_scoring_evidence.py @@ -0,0 +1,37 @@ +"""Saved file evidence must be complete before it can be rescored.""" +import hashlib +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +from harness.web_fetch.artifacts import file_scoring_metadata + + +class FileScoringEvidenceTests(unittest.TestCase): + def test_missing_delivery_is_not_malformed_evidence(self): + self.assertEqual(file_scoring_metadata({'task_kind': 'file'}, {}, Path('.')), {}) + + def test_malformed_saved_claims_fail_before_scoring(self): + for evaluation in (None, [], 'invalid', {'files': {}}, {'files': [{}]}, + {'files': [{'artifact_path': 'file'}]}): + with self.subTest(evaluation=evaluation), self.assertRaises(ValueError): + file_scoring_metadata({'task_kind': 'file'}, + {'file_evaluation': evaluation}, Path('.')) + + def test_rescore_preserves_each_delivered_file_binding(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + raw = b'retained delivered bytes' + (root / 'file.bin').write_bytes(raw) + binding = {'artifact_path': 'file.bin', 'sha256': hashlib.sha256(raw).hexdigest()} + metadata = {'file_evaluation': {'files': [binding]}} + with patch('lexbench_webfetch.file_acceptance.evaluate_files', + return_value={'files': [{'passed': False}]}): + result = file_scoring_metadata({'task_kind': 'file'}, metadata, root) + self.assertEqual(result['file_evaluation']['files'], [{**binding, 'passed': False}]) + self.assertEqual(metadata['file_evaluation']['files'], [binding]) + with patch('lexbench_webfetch.file_acceptance.evaluate_files', + return_value={'files': []}): + with self.assertRaisesRegex(ValueError, 'file count'): + file_scoring_metadata({'task_kind': 'file'}, metadata, root) diff --git a/tests/test_headless_access.py b/tests/test_headless_access.py index 7741eeb8a..81128c0a2 100644 --- a/tests/test_headless_access.py +++ b/tests/test_headless_access.py @@ -9,7 +9,11 @@ class HeadlessAccessTests(unittest.TestCase): def test_all_measured_engines_have_account_and_official_sources(self): + from harness.web_fetch.methods import browser_method_registry + for name in browser_method_registry(): + self.assertTrue(method_access(name, [])['links']) accesses = {name: method_access(name, []) for name in SPECS} + with tempfile.TemporaryDirectory() as directory: root = Path(directory) (root / 'adoption').mkdir() @@ -56,7 +60,8 @@ def test_executable_version_is_not_replaced_by_cdp_compatibility_version(self): row['provider_metadata']['reported_version']=version self.assertEqual(method_version(row),version) self.assertEqual(method_version({'provider':'chromium','provider_metadata':{'browser_version':'155.0.8059.39'}}),'155.0.8059.39') - self.assertEqual(method_version({'provider':'kitesurf','provider_metadata':{'browser_version':'145.0.0.0'}}),'Beta · 托管后端版本未公开') + self.assertNotIn('145.0.0.0',str(method_version({'provider':'kitesurf','provider_metadata':{'browser_version':'145.0.0.0'}}))) + self.assertEqual(method_version({'provider':'kitesurf','provider_metadata':{'reported_version':'service-build-9'}}),'service-build-9') if __name__ == '__main__': diff --git a/tests/test_headless_report.py b/tests/test_headless_report.py index 497b3e6ef..94f018ea2 100644 --- a/tests/test_headless_report.py +++ b/tests/test_headless_report.py @@ -1081,9 +1081,9 @@ def test_public_builder_materializes_bound_prose_and_preserves_scores(self): for track in ['web','risk_control']}, 'frozen_inputs':frozen_input_bindings(source)} repository=Path(__file__).resolve().parents[1] - with tempfile.TemporaryDirectory(prefix='report-profile-test-',dir=repository/'evaluation') as profile_dir: + with tempfile.TemporaryDirectory(prefix='report-profile-test-') as profile_dir: profile_path=Path(profile_dir)/'current-report.json' - measured['comparison']['reproduction_profile']=profile_path.relative_to(repository).as_posix() + measured['comparison']['reproduction_profile']='instances/current-report.json' measured['comparison']['report_output']='reports/synthetic-browser' (source/'dataset.json').write_text(json.dumps(measured)) profile['frozen_inputs']=frozen_input_bindings(source) diff --git a/tests/test_lexbench_dependency_boundary.py b/tests/test_lexbench_dependency_boundary.py index d0622d061..b59e97695 100644 --- a/tests/test_lexbench_dependency_boundary.py +++ b/tests/test_lexbench_dependency_boundary.py @@ -61,6 +61,10 @@ def test_frozen_definitions_have_one_installed_owner(): import json binding = json.loads((ROOT / "evaluation/lexbench-webfetch.json").read_text()) + import tomllib + dependencies = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["dependencies"] + dependency = next(value for value in dependencies if value.startswith("lexbench-webfetch @")) + assert dependency.endswith("@" + binding["revision"]), "Definition binding and installed dependency must name the same revision" for relative, expected in binding["definition_files"].items(): assert not (ROOT / relative).exists(), relative assert hashlib.sha256(definition_path(relative).read_bytes()).hexdigest() == expected diff --git a/tests/test_lexbench_report_runtime.py b/tests/test_lexbench_report_runtime.py index 8622b6163..16d73530a 100644 --- a/tests/test_lexbench_report_runtime.py +++ b/tests/test_lexbench_report_runtime.py @@ -20,6 +20,16 @@ def write_json(path, value): return hashlib.sha256(path.read_bytes()).hexdigest() +def report_profile(root): + path = root / "report-profile.json" + write_json(path, { + "functional_population": {"cases": 2, "attempts_per_case": 3}, + "resource_population": {"tasks": 1, "attempts_per_task": 1, "chart_tasks": 1, "chart_calls": 1}, + "paths": {key: str(root / key) for key in ("cohort", "source", "output", "vendor_dir", "functional_correction", "runner", "version_runs", "version_candidate", "layout_policy_runner")}, + }) + return path + + def saved_profiles(tmp_path): runner = tmp_path / "historical-inputs" (runner / "runner").mkdir(parents=True) @@ -185,6 +195,7 @@ def test_report_rejects_changed_protocol_and_partial_scope_before_output( sys.executable, *(["-O"] if optimized else []), str(root / "scripts/moli_report/build.py"), + "--profile", str(report_profile(tmp_path)), "--cohort", str(evidence / "report-cohort.json"), "--output", @@ -240,6 +251,7 @@ def test_report_rejects_certificate_paths_outside_declared_regular_evidence(tmp_ [ sys.executable, str(Path(__file__).resolve().parents[1] / "scripts/moli_report/build.py"), + "--profile", str(report_profile(tmp_path)), "--cohort", str(evidence / "report-cohort.json"), "--output", @@ -369,3 +381,61 @@ def read_bytes(path): monkeypatch.setattr(Path, "read_bytes", read_bytes) with pytest.raises(ValueError, match="results are missing their hash binding"): load_matrix(directory, {}, {}) + + +def test_report_requires_a_profile_instead_of_selecting_a_private_instance(tmp_path): + root = Path(__file__).resolve().parents[1] + result = subprocess.run([sys.executable, str(root / 'scripts/moli_report/build.py')], + cwd=tmp_path, text=True, capture_output=True) + assert result.returncode != 0 + assert '--profile' in result.stderr and 'required' in result.stderr + + +def test_profile_selects_external_evidence_and_output_without_legacy_paths(tmp_path): + root = Path(__file__).resolve().parents[1] + evidence = tmp_path / 'other-product' + write_json(evidence / 'report-cohort.json', {'strata': {}}) + write_json(evidence / 'report-runs.json', {}) + write_json(evidence / 'protocol.json', {}) + write_json(evidence / 'certification.json', {'protocol_sha256': '0' * 64}) + profile = tmp_path / 'profile.json' + write_json(profile, {'resource_population': {'tasks': 1, 'attempts_per_task': 1, + 'chart_tasks': 1, 'chart_calls': 1}, 'paths': { + 'cohort': str(evidence / 'report-cohort.json'), 'output': str(tmp_path / 'other-report'), + 'source': str(root / 'scripts/moli_report'), 'runner': str(evidence), + 'version_runs': str(evidence), 'version_candidate': str(evidence / 'candidate'), + 'vendor_dir': str(evidence), 'functional_correction': str(evidence), + 'layout_policy_runner': str(evidence)}}) + result = subprocess.run([sys.executable, str(root / 'scripts/moli_report/build.py'), + '--profile', str(profile)], cwd=tmp_path, text=True, capture_output=True) + assert result.returncode != 0 + assert 'Protocol hash mismatch' in result.stderr + assert not (tmp_path / 'other-report').exists() + + +@pytest.mark.parametrize('change', ['none', 'attempt', 'source']) +def test_comparison_reads_only_the_declared_task_bytes(tmp_path, change): + from harness.lexbench.report import read_comparison_task + frozen = b'{"task_id":"one","expected":"original"}' + path = tmp_path / 'one.json' + path.write_bytes(frozen) + digest = hashlib.sha256(frozen).hexdigest() + expected = 'a' * 64 if change == 'attempt' else digest + if change == 'source': + path.write_bytes(b'{"task_id":"one","expected":"different"}') + if change == 'none': + assert read_comparison_task([tmp_path], 'one.json', expected_sha256=expected) == frozen + else: + with pytest.raises(ValueError, match='[Tt]ask'): + read_comparison_task([tmp_path], 'one.json', expected_sha256=expected) + + +def test_comparison_selects_the_bound_version_when_a_working_copy_changed(tmp_path): + from harness.lexbench.report import read_comparison_task + first, second = tmp_path / 'workspace', tmp_path / 'frozen' + first.mkdir(); second.mkdir() + (first / 'one.json').write_bytes(b'changed workspace') + data = b'original frozen input' + (second / 'one.json').write_bytes(data) + assert read_comparison_task([first, second], 'one.json', + expected_sha256=hashlib.sha256(data).hexdigest()) == data diff --git a/tests/test_lexbench_verification.py b/tests/test_lexbench_verification.py index aa1146d61..789ba5e21 100644 --- a/tests/test_lexbench_verification.py +++ b/tests/test_lexbench_verification.py @@ -165,18 +165,20 @@ def report(tmp_path): for lang in ("zh", "en"): render_chart( combined, - site / f"assets/moli-all-success-resources-{lang}.svg", + site / f"assets/comparison-all-success-resources-{lang}.svg", ("memory", "cpu"), lang, ) render_chart( comparison, - site / f"assets/moli-version-success-resources-{lang}.svg", + site / f"assets/comparison-version-success-resources-{lang}.svg", ("pss", "cpu"), lang, ) save(site, "reproduction/plot_version_summary.py", b"# synthetic chart source\n") - save(site, "index.html", b'') + messages = (ROOT / 'scripts/moli_report/messages.json').read_text().replace('<', '\\u003c') + shell = '' + save(site, "index.html", ('' + shell + '').encode()) save(site, "en/index.html", b'') save(site, "README.md", b"Synthetic offline evidence") save(site, "app.js", (ROOT / "scripts/moli_report/app.js").read_bytes()) @@ -467,7 +469,7 @@ def test_chinese_only_report_passes_release_checks_without_english_charts(report assert not list((site / "assets").glob("*-en.svg")) -@pytest.mark.parametrize("stale", ["en/guide.html", "assets/moli-all-success-resources-en.svg"]) +@pytest.mark.parametrize("stale", ["en/guide.html", "assets/comparison-all-success-resources-en.svg"]) def test_chinese_only_report_rejects_stale_english(report, stale): site, model = chinese_only_report(report) save(site, stale, b"stale English output") diff --git a/tests/test_local_audit.py b/tests/test_local_audit.py index 0752a6913..a837e7f83 100644 --- a/tests/test_local_audit.py +++ b/tests/test_local_audit.py @@ -953,8 +953,8 @@ def test_retrospective_page_review_preserves_call_and_checks_other_requirements( missing['acceptance_contract']['stable_text_anchors'].append( {'id': 'missing', 'alternatives': ['A required fact that was not returned']}) bad = {**annotation, 'case_sha256': canonical_json_sha256(missing)} - with pytest.raises(ValueError, match='unavailable page identity'): - adjudication_evidence.score_reviewed_page_identity(tmp_path, missing, record, bad, captures) + assert not task_passed(True, adjudication_evidence.score_reviewed_page_identity( + tmp_path, missing, record, bad, captures)) def test_retrospective_page_review_requires_bound_receipt(tmp_path): diff --git a/tests/test_local_diagnostic_presentation.py b/tests/test_local_diagnostic_presentation.py index 874325440..49e5516e9 100644 --- a/tests/test_local_diagnostic_presentation.py +++ b/tests/test_local_diagnostic_presentation.py @@ -9,7 +9,7 @@ from tests.test_report_body_scope import case, record -def test_diagnostic_tables_do_not_assign_ranks_or_initial_score_order(): +def test_diagnostic_tables_sort_saved_pass_rates_without_assigning_ranks(): c = case('body', kind='body', track='web') high = record(c) high['provider'] = 'z-high' @@ -19,10 +19,11 @@ def test_diagnostic_tables_do_not_assign_ranks_or_initial_score_order(): ranking([high, low], {c['id']: c}, 'web', diagnostic=True)): soup = BeautifulSoup(rendered, 'html.parser') table = soup.select_one('table.ranking') - assert table.get('data-col') is None + assert table.get('data-col') == '2' + assert table.get('data-dir') == 'desc' assert table.select_one('th').get_text(strip=True).startswith('方法') rows = table.select('tbody tr') - assert ['a-low', 'z-high'] == [row.select_one('td a')['href'].split('/')[-1].removesuffix('.html') for row in rows] + assert ['z-high', 'a-low'] == [row.select_one('td a')['href'].split('/')[-1].removesuffix('.html') for row in rows] assert len(rows[0].select('td')) == len(table.select('th')) assert '0)' in table.select_one('th button')['onclick'] # Certified comparisons retain their existing score order and ordinal column. @@ -85,7 +86,7 @@ def test_release_check_rejects_hidden_status_or_ranked_diagnostic(tmp_path): page.write_text(prefix + banner + '
Rank
') with pytest.raises(ValueError, match='assigns ranks'): check_local_diagnostic_presentation(tmp_path) - page.write_text(prefix + banner + '
Method
') + page.write_text(prefix + banner + '
Method
') check_local_diagnostic_presentation(tmp_path) diff --git a/tests/test_local_measurement_lineage.py b/tests/test_local_measurement_lineage.py index 6733e421d..ee6b4a310 100644 --- a/tests/test_local_measurement_lineage.py +++ b/tests/test_local_measurement_lineage.py @@ -48,8 +48,7 @@ def test_unknown_verdict_can_be_shown_locally_without_becoming_publishable(): dataset = {'comparison': {'kind': 'uniform_native_local_measurement', 'publication_state': 'local_audit_only_not_for_release'}, 'coverage_plan': {'web': ['p']}, 'cases': [{'id': 'a', 'track': 'web'}]} - row = {'provider': 'p', 'case_id': 'a', 'metrics': {'page_identity_required': True, - 'page_identity_status': 'unavailable', 'request_ok': True}} + row = {'provider': 'p', 'case_id': 'a', 'metrics': {'rule_review_status': 'unresolved', 'request_ok': True}} reliability = local_audit.require_complete_local_inventory(dataset, [row]) assert reliability['present_calls'] == 1 and reliability['judged_calls'] == 0 assert not reliability['complete'] diff --git a/tests/test_local_revision.py b/tests/test_local_revision.py index 2a398b17b..47bdd0e57 100644 --- a/tests/test_local_revision.py +++ b/tests/test_local_revision.py @@ -499,7 +499,7 @@ def test_native_context_revision_replays_native_fields_and_rejects_tampering(tmp current.update(title=None, final_url=current['url']) (tmp_path/'results.jsonl').write_text(json.dumps(current)+'\n') config = {'cases': [], 'authority': {'description': 'Recover native title and redirect without new calls.'}, - 'regression_ids': ['native-title-must-match-original', 'native-returned-title-remains-answer', + 'regression_ids': ['native-title-must-match-original', 'native-returned-body-fact-with-title-remains-valid', 'native-final-url-must-match-original', 'native-returned-redirect-resolves-relative-image'], 'native_answer_corrections': [{'attempt_id': current['attempt_id'], 'native_response': {'path': 'private_artifacts/context.response.bin', 'sha256': hashlib.sha256(native).hexdigest()}, diff --git a/tests/test_moli_report_render_contract.py b/tests/test_moli_report_render_contract.py index 9cbb1db9c..4190685d0 100644 --- a/tests/test_moli_report_render_contract.py +++ b/tests/test_moli_report_render_contract.py @@ -1,5 +1,7 @@ """Exercise the report's public fixed-layout comparison components.""" +import json +import re import shutil import subprocess import unittest @@ -19,3 +21,14 @@ def test_fixed_layout_comparisons_in_both_languages(self): capture_output=True, text=True, ) + + +def test_catalog_editions_have_matching_keys_and_parameters(): + root = Path(__file__).resolve().parents[1] + catalog = json.loads((root / "scripts/moli_report/messages.json").read_text()) + english = catalog["en"]["messages"] + chinese = catalog["zh"]["messages"] + assert english.keys() == chinese.keys() + for key in english: + assert set(re.findall(r"\{value\d+\}", english[key])) == set( + re.findall(r"\{value\d+\}", chinese[key])) diff --git a/tests/test_native_browser_variants.py b/tests/test_native_browser_variants.py index 104ed26de..c22aadfa6 100644 --- a/tests/test_native_browser_variants.py +++ b/tests/test_native_browser_variants.py @@ -282,3 +282,13 @@ def test_interpretation_counts_engines_and_configurations_separately(self): self.assertIn("Moli release build", body) self.assertNotRegex(body, r"6\s*[种个]\s*浏览器") self.assertEqual(body.count('data-browser-interface='), 42) + + +def test_same_reported_version_keeps_distinct_measured_builds_visible(): + from harness.web_fetch.methods import method_versions + rows = [{'provider': 'other-browser', 'provider_metadata': { + 'reported_version': '2.4.0', 'binary_sha256': value * 64}} for value in ['a', 'b', 'a']] + assert method_versions(rows) == ['2.4.0 [aaaaaaaa, bbbbbbbb]'] + assert method_versions(rows[:1]) == ['2.4.0'] + assert method_versions([{'provider': 'other-browser', 'provider_metadata': { + 'reported_version': '2.4.0'}}]) == ['2.4.0'] diff --git a/tests/test_native_fetch_diagnostics.py b/tests/test_native_fetch_diagnostics.py index 45abc4e32..ffd6f23b8 100644 --- a/tests/test_native_fetch_diagnostics.py +++ b/tests/test_native_fetch_diagnostics.py @@ -28,7 +28,8 @@ def test_ambiguous_response_or_navigation_does_not_supply_status(log): @pytest.mark.parametrize('requested,observed,page_matches,log_status', [ (URL, URL, True, 200), - (URL, URL+'?tracking=1', True, None), + (URL, URL+'?utm_source=newsletter', True, None), + (URL, URL+'?tracking=1', False, None), (URL+'?page=1', URL+'?page=1', True, 200), (URL+'?page=1', URL+'?page=2', False, None), ]) diff --git a/tests/test_providers.py b/tests/test_providers.py index 56c708eff..f8978e436 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -39,7 +39,7 @@ def test_native_final_url_controls_identity(self, request): for native_url, passed, measured, identity in ( ('https://example.test/final',True,True,'verified'), ('https://example.test/other',False,True,'mismatch'), - (None,False,False,'unavailable'), + (None,False,True,'unavailable'), ): with self.subTest(native_url=native_url): metadata={'sourceURL':case.url,'statusCode':200} diff --git a/tests/test_public_report_language.py b/tests/test_public_report_language.py index 52ba8b160..a0718979c 100644 --- a/tests/test_public_report_language.py +++ b/tests/test_public_report_language.py @@ -131,7 +131,9 @@ def test_make_language_override_is_only_explicit(tmp_path, monkeypatch, target, make = shutil.which("make") if not make or subprocess.run([make, "--version"], capture_output=True).returncode: pytest.skip("Make is not installed") - args = [make, "--no-print-directory", "-n", "-f", str(ROOT / "Makefile"), target, "EVIDENCE=" + str(tmp_path), "OUTPUT=" + str(tmp_path / "report")] + profile = tmp_path / "instance.json" + profile.write_text(json.dumps({"providers": [], "frozen_inputs": True})) + args = [make, "PROFILE=" + str(profile), "--no-print-directory", "-n", "-f", str(ROOT / "Makefile"), target, "EVIDENCE=" + str(tmp_path), "OUTPUT=" + str(tmp_path / "report")] if language: args += ["REPORT_LANGUAGE=" + language] env = {key: value for key, value in os.environ.items() if key not in {"REPORT_LANGUAGE", "MAKEFLAGS"}} diff --git a/tests/test_publication_privacy.py b/tests/test_publication_privacy.py index ed39989e6..0c575b4ff 100644 --- a/tests/test_publication_privacy.py +++ b/tests/test_publication_privacy.py @@ -605,7 +605,7 @@ def interrupted(*args): # Reject a different cohort or an attempt/log rewrite. saved_results = (source / 'results.jsonl').read_bytes() (source / 'results.jsonl').write_bytes(b'changed results') - with self.assertRaisesRegex(ValueError, 'cannot replace measured results'): + with self.assertRaises(ValueError): module.build_incremental(source, archive_path, root / 'wrong-cohort') (source / 'results.jsonl').write_bytes(saved_results) (source / 'report-manifest.json').write_text(json.dumps({'presentation_refresh': { @@ -933,3 +933,25 @@ def test_review_projection_does_not_hide_risks_in_unrelated_metadata(): files = {'local-evidence/review/supplement.json': raw} public, _ = project_host_metadata_files(files, ['/Users/fixture'], public_excerpt_views=True) assert public == files and publication_risk(public['local-evidence/review/supplement.json']) + + +def test_signed_thumbnail_redaction_preserves_outer_record_and_following_text(): + """A private image query must not consume the record's closing Markdown.""" + from lexbench_webfetch.scoring import score_attempt + from lexbench_webfetch.metrics import task_passed + case={'url':'https://example.test/','acceptance_contract':{'version':1, + 'schema':'named_link','minimum_records':2,'hosts':['example.test'], + 'entry_path':r'/boards/[0-9]+','stable_text_anchors':[{'id':'feature','alternatives':['Collect and organize']} ]}} + original=( + '[![Cover](https://cdn.example.test/cover.jpg?auth_key=fictional-one)Collection One](https://example.test/boards/1)\n' + '[![Cover](https://cdn.example.test/cover2.jpg?auth_key=fictional-two)Collection Two](https://example.test/boards/2)\n' + 'Collect and organize\n').encode() + projected,receipt=project_file('artifacts/sample/boards.txt',original) + assert b'fictional-one' not in projected and b'fictional-two' not in projected + assert receipt['replacements']==2 + assert task_passed(True,score_attempt(case,original,ok=True)) + scored=score_attempt(case,projected,ok=True) + assert task_passed(True,scored) + assert scored['stable_unique_record_count']==2 + broken=projected.replace(b')Collection One](https://example.test/boards/1)',b'') + assert not task_passed(True,score_attempt(case,broken,ok=True)) diff --git a/tests/test_published_snapshot_report.py b/tests/test_published_snapshot_report.py new file mode 100644 index 000000000..9ae8ebca1 --- /dev/null +++ b/tests/test_published_snapshot_report.py @@ -0,0 +1,175 @@ +"""A saved report matrix rebuilds through the maintained WebFetch renderer.""" +import json + +import pytest + +from harness.record_io import canonical_json_sha256, file_sha256 +from harness.web_fetch.published_snapshot import build_snapshot_audit, check_snapshot_audit, validate_snapshot_audit +from harness.web_fetch.report_explainer import _headless_article_input +from harness.web_fetch.rescoring import score_published_snapshot +from lexbench_webfetch.scoring import score_attempt + + +def example(root, recover_original=False): + source=root/'source';source.mkdir() + cases=[{'id':key,'name':key,'url':'https://example.test/'+key,'track':'web','category':'web', + 'required_facts':[{'id':'body','alternatives':['complete answer']}]} for key in ['complete','missing']] + dataset={'id':'published-example','name':'Published example','cases':cases, + 'coverage_plan':{'web':['chromium']},'comparison':{'kind':'headless_markdown', + 'timeout_seconds':5,'runs_per_case':1,'concurrency':1,'max_failure_retries':0}} + rows=[] + for case in cases: + raw=b'complete answer' if case['id']=='complete' else b'partial output' + path=source/'artifacts'/f'{case["id"]}.txt';path.parent.mkdir(exist_ok=True);path.write_bytes(raw) + reference=source/'references'/f'{case["id"]}.txt';reference.parent.mkdir(exist_ok=True);reference.write_text('complete answer') + rows.append({'provider':'chromium','provider_type':'fixture','case_id':case['id'],'case_name':case['name'], + 'category':'web','attempt_id':'chromium__'+case['id']+'__r001','run':1,'run_id':'call-'+case['id'], + 'url':case['url'],'started_at':'2026-10-01T00:00:00Z','elapsed_ms':100, + 'ok':True,'status_code':200,'title':None,'final_url':case['url'],'error_type':None, + 'error_message':None,'provider_metadata':{},'artifact_path':path.relative_to(source).as_posix(), + 'metrics':score_attempt(case,raw,ok=True)}) + rows[0]['metrics']['content_pass']=False + (source/'dataset.json').write_text(json.dumps(dataset)) + (source/'results.jsonl').write_text(''.join(json.dumps(row)+'\n' for row in rows)) + (source/'execution.json').write_text(json.dumps({'batches':[{'timeout_seconds':5,'attempt_ids':[row['attempt_id'] for row in rows]}]})) + privacy=[];recovered={} + if recover_original: + artifact=source/rows[0]['artifact_path'];original=root/'recovered.txt';original.write_bytes(artifact.read_bytes()) + artifact.write_bytes(original.read_bytes()+b' [REDACTED]') + privacy.append({'path':rows[0]['artifact_path'],'original_sha256':file_sha256(original), + 'published_sha256':file_sha256(artifact),'published_bytes':artifact.stat().st_size,'scored_output':True}) + recovered[rows[0]['artifact_path']]=original + (source/'publication-privacy.json').write_text(json.dumps({'files':privacy})) + result,receipt=score_published_snapshot(source,dataset,recovered=recovered) + manifest={'files':{p.relative_to(source).as_posix():{'sha256':file_sha256(p)} for p in source.rglob('*') if p.is_file()}} + def save(name,value): + path=root/name;path.write_text(json.dumps(value,ensure_ascii=False));return {'path':name,'sha256':file_sha256(path)} + review={'title':'Saved output review','summary':'One corrected serialization decision.', + 'figure_caption':'Saved output and acceptance rules.','metric_note':'Two saved calls, no new calls.', + 'focus_heading':'Sample configuration','focus_summary':'One correction.', + 'privacy_summary':'All original bytes are present.','privacy_findings':[], + 'review_scope':'One of two results reviewed.','total_attempts':2,'reviewed_attempts':1, + 'providers':[{'provider':'chromium','published_passes':0,'historical_passes':0,'initial_rescore_passes':0,'corrected_passes':1,'total':2}], + 'changes':[{'attempt_id':result[0]['attempt_id'],'provider':'chromium','case_id':'complete', + 'historical_pass':False,'initial_rescore_pass':False,'after_fix_pass':True, + 'classification':'Serialization defect','requirement':'Return the complete answer.', + 'actual':'complete answer','reason':'The saved answer includes the required text.', + 'treatment':'Use the shared scorer.','source_scope':'Frozen fixture reference.','evidence':[],'quotes':[]}]} + article={'schema':'webfetch_browser_interpretation/1','evidence_sha256':canonical_json_sha256(_headless_article_input({c['id']:c for c in cases},result,['chromium'])), + 'title':'Saved result interpretation','intro':'Two saved calls under the same contract.', + 'scoring':'Required text must occur in the delivered answer.','conclusions':'One answer meets its contract.', + 'providers':{'chromium':{'specimen_case_id':'missing','explanation':'The partial output lacks the required text.'}}} + inputs={'dataset':save('corrected.json',dataset),'receipt':save('receipt.json',receipt), + 'review':save('review.json',review),'interpretation':save('interpretation.json',article), + 'snapshot_manifest':save('manifest.json',manifest), + 'historical_verdicts':save('historic.json',{'verdicts':{r['attempt_id']:False for r in rows}})} + initial=root/'initial.jsonl';initial.write_text(''.join(json.dumps(row)+'\n' for row in rows)) + inputs['initial_results']={'path':initial.name,'sha256':file_sha256(initial)} + result_path=root/'corrected.jsonl';result_path.write_text(''.join(json.dumps(row)+'\n' for row in result)) + inputs['results']={'path':result_path.name,'sha256':file_sha256(result_path)} + figure=root/'figure.png';figure.write_bytes(b'fixture image');inputs['figure']={'path':figure.name,'sha256':file_sha256(figure)} + profile={'variant':'published-snapshot-audit','certified':False,'inputs':inputs, + 'source':{'url':'https://example.test/report','version':'frozen','archive_sha256':'0'*64}, + 'correction_provenance':{'category':'format-normalization','root_cause':'Visible native label boundaries must preserve complete declared words.', + 'regression_ids':['native-serialization-stock-label','native-serialization-stock-word-fragment']}} + path=root/'profile.json';path.write_text(json.dumps(profile));return source,path + + +@pytest.mark.parametrize("recover_original",[False,True]) +def test_saved_matrix_keeps_native_routes_and_inspectable_review(tmp_path,recover_original): + source,profile=example(tmp_path,recover_original=recover_original);before={p.relative_to(source):p.read_bytes() for p in source.rglob('*') if p.is_file()} + out=tmp_path/'report';build_snapshot_audit(source,out,profile=profile) + assert '2 saved calls' in check_snapshot_audit(out) + from harness.web_fetch.report_package import validate_release + with pytest.raises(ValueError,match='require --snapshot-audit'):validate_release(out) + assert validate_release(out,snapshot_audit=True) is False + assert not (out/'publication-privacy.json').exists() + assert (out/'audit/history/publication-privacy.json').is_file() + from harness.web_fetch.report_package import build + from harness.web_fetch.publication_check import check + archive=tmp_path/'report.zip';build(out,archive,snapshot_audit=True) + check(archive,out,snapshot_audit=True) + for path in ['index.html','guide/index.html','datasets/index.html','analysis/index.html','attempts/chromium__complete__r001.html','attempts/chromium__missing__r001.html','en/index.html']: + assert (out/path).is_file() + assert {p.relative_to(source):p.read_bytes() for p in source.rglob('*') if p.is_file()}==before + from bs4 import BeautifulSoup + for route in ['index.html','guide/index.html','analysis/index.html','audit/index.html']: + page=BeautifulSoup((out/route).read_text(),'html.parser') + assert page.select_one('main[data-evidence-state]') is not None + assert not page.select('[role="status"][data-evidence-state]') + for unwanted in ['保存输出的评分复核','本地未认证诊断结果','任务判定汇总(非排名)']: + assert unwanted not in page.get_text() + method=out/'methodology/index.html';original=method.read_text() + method.write_text(original.replace('data-scoring-input-scope','data-removed-scope')) + with pytest.raises(ValueError,match='lost its scoring input scope'):check_snapshot_audit(out) + method.write_text(original) + detail=out/'attempts/chromium__complete__r001.html';detail.write_text(detail.read_text().replace('data-score-audit','data-deleted-audit')) + with pytest.raises(ValueError,match='lost its audit explanation'):check_snapshot_audit(out) + + +@pytest.mark.parametrize('defect',['extra-file','changed-source','changed-call','wrong-view','wrong-count','review-omission']) +def test_snapshot_boundary_rejects_unbound_evidence_and_call_changes(tmp_path,defect): + source,profile=example(tmp_path);config=json.loads(profile.read_text()) + if defect=='extra-file':(source/'private-note.txt').write_text('undeclared') + elif defect=='changed-source':(source/'artifacts/complete.txt').write_text('tampered') + elif defect in {'wrong-count','review-omission'}: + p=tmp_path/config['inputs']['review']['path'];review=json.loads(p.read_text()) + if defect=='wrong-count':review['providers'][0]['corrected_passes']=2 + else:review['changes']=[];review['reviewed_attempts']=0 + p.write_text(json.dumps(review));config['inputs']['review']['sha256']=file_sha256(p);profile.write_text(json.dumps(config)) + else: + p=tmp_path/config['inputs']['results']['path'];rows=[json.loads(x) for x in p.read_text().splitlines()] + if defect=='changed-call':rows[0]['elapsed_ms']=1 + else:rows[0]['scoring']['artifact_view']='published_projection' + p.write_text(''.join(json.dumps(row)+'\n' for row in rows));config['inputs']['results']['sha256']=file_sha256(p);profile.write_text(json.dumps(config)) + with pytest.raises(ValueError):validate_snapshot_audit(source,profile) + + +def test_snapshot_package_uses_the_shared_publisher_without_certification(tmp_path): + from dataclasses import replace + from harness.web_fetch.report_package import build + from harness.web_fetch.report_publish import PublishConfig, publish + source,profile=example(tmp_path);out=tmp_path/'report' + build_snapshot_audit(source,out,profile=profile);archive=tmp_path/'report.zip' + build(out,archive,snapshot_audit=True) + target=tmp_path/'publication.json';target.write_text(json.dumps({'artifact_hub_base_url':'https://example.test'})) + config=PublishConfig(target,archive,out,tmp_path/'publication-receipt.json',tmp_path/'operation.json',mode='create',visibility='private') + def transport(path,data=None,content_type=None,**kwargs): + if path=='/api/sites':return 200,json.dumps({'slug':'new-audit','versionId':'ver_new'}).encode() + if path=='/api/sites/new-audit/sharing':return 200,b'{"visibility":"private"}' + raise AssertionError(path) + with pytest.raises(ValueError,match='require --snapshot-audit'):publish(config,transport=transport) + result=publish(replace(config,snapshot_audit=True),transport=transport) + assert result['visibility']=='private' and result['url']=='https://example.test/s/new-audit' + + +@pytest.mark.parametrize('defect',[None,'same-id','different-rule','different-parent']) +def test_annotation_version_is_distinct_and_matches_report_rules(tmp_path,defect): + source,profile=example(tmp_path);config=json.loads(profile.read_text()) + dataset_path=tmp_path/config['inputs']['dataset']['path'];dataset=json.loads(dataset_path.read_text()) + parent=dataset['id'];dataset['id']=parent+'--annotation-v2' + dataset['cases'][0]['diagnostic_any']=['source terminology'] + revision={'id':'annotation-v2','changes':[{'case_id':'complete','set':{'diagnostic_any':['source terminology']},'remove':[]}]} + revision_path=tmp_path/'annotation.json';revision_path.write_text(json.dumps(revision)) + config['inputs']['annotation_revision']={'path':revision_path.name,'sha256':file_sha256(revision_path)} + dataset['definition_revision']={'id':'annotation-v2','path':'audit/annotation-definition.json', + 'sha256':file_sha256(revision_path),'parent_dataset_id':parent, + 'parent_dataset_sha256':file_sha256(source/'dataset.json')} + if defect=='same-id':dataset['id']=parent + elif defect=='different-rule':dataset['cases'][0]['diagnostic_any']=['unbound terminology'] + elif defect=='different-parent':dataset['definition_revision']['parent_dataset_sha256']='0'*64 + dataset_path.write_text(json.dumps(dataset));config['inputs']['dataset']['sha256']=file_sha256(dataset_path) + receipt=tmp_path/config['inputs']['receipt']['path'];data=json.loads(receipt.read_text()) + data['corrected_dataset_sha256']=canonical_json_sha256(dataset);receipt.write_text(json.dumps(data));config['inputs']['receipt']['sha256']=file_sha256(receipt) + article=tmp_path/config['inputs']['interpretation']['path'];data=json.loads(article.read_text()) + rows=[json.loads(line) for line in (tmp_path/config['inputs']['results']['path']).read_text().splitlines()] + data['evidence_sha256']=canonical_json_sha256(_headless_article_input({c['id']:c for c in dataset['cases']},rows,['chromium'])) + article.write_text(json.dumps(data));config['inputs']['interpretation']['sha256']=file_sha256(article);profile.write_text(json.dumps(config)) + output=tmp_path/'versioned-report' + if defect: + with pytest.raises(ValueError,match='Annotation revision|annotation version'):build_snapshot_audit(source,output,profile=profile) + assert not output.exists() + else: + build_snapshot_audit(source,output,profile=profile) + assert '2 saved calls' in check_snapshot_audit(output) + assert (output/'audit/annotation-definition.json').read_bytes()==revision_path.read_bytes() diff --git a/tests/test_published_snapshot_rescoring.py b/tests/test_published_snapshot_rescoring.py new file mode 100644 index 000000000..7b2cee1ea --- /dev/null +++ b/tests/test_published_snapshot_rescoring.py @@ -0,0 +1,74 @@ +"""Public-copy rescoring must disclose changed bytes and preserve call facts.""" +import hashlib +import json + +import pytest + +from harness.web_fetch.rescoring import score_published_snapshot +from lexbench_webfetch.metrics import task_passed +from lexbench_webfetch.scoring import score_attempt + + +def snapshot(tmp_path): + case = {'id': 'one', 'name': 'One', 'url': 'https://example.test/', + 'category': 'web', 'track': 'web', 'required_facts': [ + {'id': 'fact', 'alternatives': ['complete answer']}]} + raw = b'complete answer' + public = b'[REDACTED]' + original = tmp_path / 'original.txt' + original.write_bytes(raw) + source = tmp_path / 'source' + source.mkdir() + (source / 'answer.txt').write_bytes(public) + dataset = {'cases': [case], 'coverage_plan': {'web': ['sample']}} + record = {'attempt_id': 'sample__one__r001', 'provider': 'sample', + 'case_id': 'one', 'case_name': 'One', 'category': 'web', + 'url': case['url'], 'run': 1, 'ok': True, 'elapsed_ms': 43.5, + 'run_id': 'unchanged-call', 'artifact_path': 'answer.txt', + 'provider_metadata': {}, 'metrics': score_attempt(case, raw, ok=True)} + (source / 'dataset.json').write_text(json.dumps(dataset)) + (source / 'results.jsonl').write_text(json.dumps(record) + '\n') + receipt = {'files': [{'path': 'answer.txt', 'scored_output': True, + 'original_sha256': hashlib.sha256(raw).hexdigest(), + 'published_sha256': hashlib.sha256(public).hexdigest(), + 'published_bytes': len(public)}]} + (source / 'publication-privacy.json').write_text(json.dumps(receipt)) + return source, dataset, record, original + + +@pytest.mark.parametrize('recover', [False, True]) +def test_exact_public_or_recovered_input_is_explicit(tmp_path, recover): + source, dataset, old, original = snapshot(tmp_path) + before = {p.name: p.read_bytes() for p in source.iterdir()} + rows, receipt = score_published_snapshot(source, dataset, + recovered={'answer.txt': original} if recover else None) + row = rows[0] + assert task_passed(row['ok'], row['metrics']) is recover + assert row['scoring']['artifact_view'] == ('recovered_original' if recover else 'published_projection') + assert receipt['projected_scoring_inputs'] == (0 if recover else 1) + assert receipt['provider_calls_added'] == 0 + assert {k: v for k, v in old.items() if k != 'metrics'} == { + k: v for k, v in row.items() if k not in {'metrics', 'scoring'}} + assert {p.name: p.read_bytes() for p in source.iterdir()} == before + + +@pytest.mark.parametrize('defect', ['public_hash', 'original_hash', 'size', 'recovered_hash', 'matrix', 'input']) +def test_unbound_or_changed_evidence_cannot_be_scored(tmp_path, defect): + source, dataset, _, original = snapshot(tmp_path) + recovered = None + receipt_path = source / 'publication-privacy.json' + receipt = json.loads(receipt_path.read_text()) + if defect in {'public_hash', 'original_hash'}: + receipt['files'][0]['published_sha256' if defect == 'public_hash' else 'original_sha256'] = '0' * 64 + elif defect == 'size': + receipt['files'][0]['published_bytes'] += 1 + elif defect == 'recovered_hash': + original.write_bytes(b'other answer') + recovered = {'answer.txt': original} + elif defect == 'matrix': + dataset['cases'] = [] + else: + dataset['cases'][0]['url'] = 'https://example.test/other' + receipt_path.write_text(json.dumps(receipt)) + with pytest.raises(ValueError): + score_published_snapshot(source, dataset, recovered=recovered) diff --git a/tests/test_reliability.py b/tests/test_reliability.py index 2178ee764..4f656a794 100644 --- a/tests/test_reliability.py +++ b/tests/test_reliability.py @@ -16,6 +16,38 @@ def test_complete_plan_is_reliable(self): self.assertTrue(result['complete']) self.assertEqual(result['judged_calls'],1) + def test_failure_only_reruns_retain_successes_and_disclose_build_counts(self): + dataset = {**self.dataset, 'cases': [self.dataset['cases'][0], + {'id': 'b', 'track': 'web', 'task_kind': 'body'}]} + rows = [{**self.record, 'provider_metadata': {'binary_sha256': 'first-build'}}, + {**self.record, 'case_id': 'b', + 'provider_metadata': {'binary_sha256': 'replacement-build'}}] + result = require_reliable(dataset, rows) + self.assertEqual(result['implementation_inventory']['p'], + {'first-build': 1, 'replacement-build': 1}) + rows[1]['provider_metadata']['binary_sha256'] = 'first-build' + self.assertTrue(require_reliable(dataset, rows)['complete']) + + def test_inventory_counts_cloud_unknown_and_retained_build_results(self): + from harness.web_fetch.reliability import implementation_inventory + rows = [ + {'provider': 'cloud', 'provider_metadata': {}}, + {'provider': 'cloud', 'provider_metadata': {'reported_version': 'service-v2'}}, + {'provider': 'local', 'provider_metadata': {'binary_sha256': 'build-a'}}, + {'provider': 'local', 'provider_metadata': None}, + ] + inventory = implementation_inventory(rows) + self.assertEqual(inventory['cloud'], {'unknown': 1, 'reported-version:service-v2': 1}) + self.assertEqual(inventory['local'], {'build-a': 1, 'unknown': 1}) + self.assertEqual(sum(sum(counts.values()) for counts in inventory.values()), len(rows)) + + def test_separately_named_builds_can_be_compared_on_the_complete_population(self): + dataset = {**self.dataset, 'coverage_plan': {'web': ['release', 'candidate']}} + rows = [{**self.record, 'provider': name, + 'provider_metadata': {'binary_sha256': name + '-build'}} + for name in ('release', 'candidate')] + self.assertTrue(require_reliable(dataset, rows)['complete']) + def test_missing_duplicate_and_unjudged_are_rejected(self): self.assertFalse(assess_reliability(self.dataset,[])['complete']) duplicate=assess_reliability(self.dataset,[self.record,self.record]) diff --git a/tests/test_remaining_public_boundaries.py b/tests/test_remaining_public_boundaries.py index d70939261..d1a14aabb 100644 --- a/tests/test_remaining_public_boundaries.py +++ b/tests/test_remaining_public_boundaries.py @@ -73,7 +73,7 @@ def test_existing_evidence_is_preserved_before_loading_inputs(tmp_path, entry): '--verified-commit', 'b']), 'catalog-actions': (main_audit, ['--catalog', 'missing', '--portable-suite', 'missing', '--action-overrides', 'missing']), - 'replay-merge': (main_merge, ['--seeds', 'missing', '--qualified-dataset', 'missing', + 'replay-merge': (main_merge, ['--profile', 'missing', '--seeds', 'missing', '--qualified-dataset', 'missing', '--base-receipt', 'missing']), } function, args = commands[entry] diff --git a/tests/test_report_assets.py b/tests/test_report_assets.py index dd82596c2..582092127 100644 --- a/tests/test_report_assets.py +++ b/tests/test_report_assets.py @@ -117,3 +117,20 @@ def test_bound_copy_rejects_user_created_parent_links_before_writes(tmp_path, si {'reference.txt': hashlib.sha256(payload).hexdigest()}) assert not destination.exists() assert (source / 'reference.txt').read_bytes() == payload + + +def test_selected_json_assets_are_optional_bound_and_external(tmp_path): + from harness.reporting.assets import bound_json_asset + assert bound_json_asset(None, root=tmp_path) == (None, {}) + source = tmp_path / 'instance-history.json' + source.write_text('{"releases": []}') + binding = {'path': str(source), 'sha256': hashlib.sha256(source.read_bytes()).hexdigest()} + assert bound_json_asset(binding, root=tmp_path)[1] == {'releases': []} + assert bound_json_asset({**binding, 'path': source.name}, root=tmp_path)[1] == {'releases': []} + source.write_text('{"releases": ["unrelated report"]}') + with pytest.raises(ValueError, match='differs'): + bound_json_asset(binding, root=tmp_path) + alias = tmp_path/'alias.json' + alias.symlink_to(source) + with pytest.raises(ValueError, match='[Ss]ymlink'): + bound_json_asset({**binding, 'path': str(alias)}, root=tmp_path) diff --git a/tests/test_report_body_scope.py b/tests/test_report_body_scope.py index c2890f4ed..2d663d01c 100644 --- a/tests/test_report_body_scope.py +++ b/tests/test_report_body_scope.py @@ -133,7 +133,8 @@ def test_media_report_families_follow_the_deliverable(self): self.assertEqual(REPORT_FAMILIES['media_file'], '直接媒体文件') def test_task_family_names_and_order_share_the_report_registry(self): - self.assertEqual(list(TRACKS), ['web','dynamic','challenge','risk_control','document','image','video']) + from lexbench_webfetch.models import TRACKS as canonical_tracks + self.assertEqual(list(TRACKS.items()), list(canonical_tracks.items())) self.assertEqual(TRACKS['risk_control'], '风控网页正文') def test_all_call_latency_keeps_failed_calls_and_rejects_partial_matrix(self): diff --git a/tests/test_report_capabilities.py b/tests/test_report_capabilities.py index a2a9c0b7b..63ec861e5 100644 --- a/tests/test_report_capabilities.py +++ b/tests/test_report_capabilities.py @@ -54,7 +54,7 @@ def test_source_backed_capabilities_link_to_current_pinned_revisions(self): self.assertTrue(SOURCES['moli-release'][1].endswith('/releases/tag/v1.1.4')) def test_all_evaluated_interfaces_have_scoped_evidence(self): - self.assertEqual(set(PROFILES), set(CAPABILITIES)) + self.assertTrue(set(PROFILES) <= set(CAPABILITIES)) for name, row in CAPABILITIES.items(): self.assertTrue(row['scope'], name) self.assertTrue(row['tested'], name) diff --git a/tests/test_report_check_dispatch.py b/tests/test_report_check_dispatch.py index f17946703..59e51b345 100644 --- a/tests/test_report_check_dispatch.py +++ b/tests/test_report_check_dispatch.py @@ -16,8 +16,13 @@ def test_webmainbench_sample_is_not_routed_to_wcxb(tmp_path, command): (tmp_path / "dataset.json").write_text( json.dumps({"comparison": {"kind": "webmainbench_frozen_html_to_markdown"}, "cases": []}) ) + profile = tmp_path / 'selected-profile.json' + profile.write_text(json.dumps({'comparison_kind': 'webmainbench_frozen_html_to_markdown'})) + (tmp_path / 'index.html').write_text('') + from harness.report_languages import write_language_contract + write_language_contract(tmp_path, include_english=False) result = subprocess.run( - [sys.executable, str(ROOT / "scripts" / command), str(tmp_path)], + [sys.executable, str(ROOT / "scripts" / command), str(tmp_path), "--profile", str(profile)], capture_output=True, text=True, ) @@ -32,9 +37,12 @@ def test_wcxb_report_does_not_require_web_fetch_dataset_file(tmp_path, command): (tmp_path / "sample-manifest.json").write_text(json.dumps({"cases": []})) (tmp_path / "wcxb-score-summary.json").write_text("{}") (tmp_path / "index.html").write_text('

Synthetic WCXB report

') - write_inventory(tmp_path, json.loads((ROOT / "evaluation/wcxb/current-report.json").read_bytes())) + profile = tmp_path / "selected-profile.json" + profile.write_text(json.dumps({"comparison_kind": "wcxb_frozen_html_to_markdown"})) + (tmp_path / "report-content-binding.json").write_text("{}") + write_inventory(tmp_path, json.loads(profile.read_bytes())) result = subprocess.run( - [sys.executable, str(ROOT / "scripts" / command), str(tmp_path)], + [sys.executable, str(ROOT / "scripts" / command), str(tmp_path), "--profile", str(profile)], capture_output=True, text=True, ) diff --git a/tests/test_report_check_owner.py b/tests/test_report_check_owner.py index e8231a64d..f0401e851 100644 --- a/tests/test_report_check_owner.py +++ b/tests/test_report_check_owner.py @@ -159,14 +159,17 @@ def test_explicit_wcxb_profile_dispatches_to_wcxb_gate(tmp_path): (tmp_path / "wcxb-score-summary.json").write_text("{}") write_json(tmp_path / "sample-manifest.json", {"cases": []}) (tmp_path / "index.html").write_text('

Synthetic WCXB report

') - write_inventory(tmp_path, json.loads((SCRIPT.parents[1] / "evaluation/wcxb/current-report.json").read_bytes())) + profile = tmp_path / "selected-profile.json" + profile.write_text(json.dumps({"comparison_kind": "wcxb_frozen_html_to_markdown"})) + (tmp_path / "report-content-binding.json").write_text("{}") + write_inventory(tmp_path, json.loads(profile.read_bytes())) result = subprocess.run( [ sys.executable, str(SCRIPT), str(tmp_path), "--profile", - "evaluation/wcxb/current-report.json", + str(profile), ], capture_output=True, text=True, diff --git a/tests/test_report_content_ownership.py b/tests/test_report_content_ownership.py index 34580ff14..b355cce60 100644 --- a/tests/test_report_content_ownership.py +++ b/tests/test_report_content_ownership.py @@ -34,6 +34,15 @@ def test_every_track_explains_meaning_example_and_boundary(self): self.assertIsNone(soup.select_one('.track-flow').find_parent('details')) self.assertTrue(soup.select_one('.track-flow p').get_text().strip()) + def test_all_registered_task_guides_and_labels_have_english_editions(self): + from pathlib import PurePosixPath + from harness.web_fetch.report_i18n import TEXT, localize_html, untranslated_english_narrative + for track, label in TRACKS.items(): + with self.subTest(track=track): + self.assertIn(label, TEXT) + page = localize_html(task_pass_guide(track), PurePosixPath('datasets/index.html')) + self.assertEqual(untranslated_english_narrative(page), []) + def test_rendering_and_value_changes_are_not_confused(self): text=task_pass_guide('dynamic') for phrase in ['不是文字经常更新','异步取数','客户端组装','嵌入子页','JavaScript 不是唯一','不要求滚动']: diff --git a/tests/test_report_locales.py b/tests/test_report_locales.py new file mode 100644 index 000000000..13e3c6802 --- /dev/null +++ b/tests/test_report_locales.py @@ -0,0 +1,25 @@ +"""Catalog-driven messages do not require a branch for each language.""" +import json + +import pytest + +from harness.reporting.locales import translate + + +def test_messages_select_languages_and_preserve_model_values(tmp_path): + path = tmp_path / "messages.json" + path.write_text(json.dumps({ + "en": {"count": "{passed}/{total} tasks", "columns": ["Task", "Result"]}, + "zh": {"count": "{passed}/{total} 题", "columns": ["题目", "结果"]}, + "fr": {"count": "{passed}/{total} tâches", "columns": ["Tâche", "Résultat"]}, + })) + assert translate(path, "count", "en", passed=2, total=3) == "2/3 tasks" + assert translate(path, "count", "zh", passed=2, total=3) == "2/3 题" + assert translate(path, "count", "fr", passed=2, total=3) == "2/3 tâches" + assert translate(path, "columns", "fr") == ["Tâche", "Résultat"] + with pytest.raises(ValueError, match="de/count"): + translate(path, "count", "de", passed=2, total=3) + with pytest.raises(ValueError, match="en/unknown"): + translate(path, "unknown", "en") + with pytest.raises(KeyError): + translate(path, "count", "en", passed=2) diff --git a/tests/test_report_method_registry.py b/tests/test_report_method_registry.py index b4b3a11d0..05bfca3d5 100644 --- a/tests/test_report_method_registry.py +++ b/tests/test_report_method_registry.py @@ -14,7 +14,8 @@ @pytest.fixture def custom_catalog(monkeypatch): - catalog = copy.deepcopy(report_method_catalog()) + from harness.web_fetch.methods import _raw_report_method_catalog, _catalog_value + catalog = copy.deepcopy(_raw_report_method_catalog()) catalog['methods']['acme-fetch'] = { 'label': {'zh': 'Acme Fetch', 'en': 'Acme Fetch'}, 'kind': 'browser', 'label_template': '{label} {version}', @@ -28,8 +29,8 @@ def custom_catalog(monkeypatch): 'request': {'url': 'https://example.com/'}, 'source': 'https://example.com/acme', 'notes': 'Synthetic registration.'}, } - monkeypatch.setattr('harness.web_fetch.methods.report_method_catalog', lambda language="zh": catalog) - monkeypatch.setattr('harness.web_fetch.report_interfaces.report_method_catalog', lambda language="zh": catalog) + monkeypatch.setattr('harness.web_fetch.methods.report_method_catalog', lambda language="zh": _catalog_value(catalog, language=language)) + monkeypatch.setattr('harness.web_fetch.report_interfaces.report_method_catalog', lambda language="zh": _catalog_value(catalog, language=language)) return catalog @@ -414,9 +415,10 @@ def violations(value, path=()): return [item for index, child in enumerate(value) for item in violations(child, (*path, str(index)))] bound = any(fnmatchcase('.'.join(path), rule) for rule in ( - 'adapters.*.report.*', 'methods.*.runtime_parameters.*.rules.*')) + 'adapters.*.report.*', 'methods.*.runtime_parameters.*.rules.*', 'messages.*')) return [path] if isinstance(value, str) and re.search('本轮|this run', value, re.I) and not bound else [] assert not violations(data) + assert not violations({'messages': {'run_label': {'zh': '本轮输出', 'en': 'This run’s output'}}}) for text in ('本轮使用默认参数', 'THIS RUN uses a timeout'): assert violations({'methods': {'new': {'profile': {'description': text}}}}) assert not violations({'adapters': {'new': {'report': {'description': text}}}}) @@ -450,3 +452,27 @@ def render(out, dataset, records): return browser_method_label("moli", language="en") assert render(tmp_path, {}, [row]) == "new-engine" + (" 2.3.4" if version else "") + + +@pytest.mark.parametrize("provider, version, expected", [ + ("obscura", "obscura 0.2.2", "Obscura 0.2.2"), + ("lightpanda", "Lightpanda 0.4.1+build.7", "Lightpanda 0.4.1+build.7"), + ("obscura", "0.2.2 (obscura-compatible)", "Obscura 0.2.2 (obscura-compatible)"), + ("obscura", "ObscuraTools 2.0", "Obscura ObscuraTools 2.0"), +]) +def test_reported_product_name_is_not_repeated_or_removed_from_build_suffix(tmp_path, provider, version, expected): + @with_measured_method_labels + def render(out, dataset, records): + return tuple(browser_method_label(provider, language=language) for language in ('zh', 'en')) + records = [{'provider': provider, 'provider_metadata': {'engine': provider, 'reported_version': version}}] + assert render(tmp_path, {}, records) == (expected, expected) + + +def test_method_message_languages_preserve_parameter_contracts(): + from string import Formatter + from harness.web_fetch.methods import _raw_report_method_catalog + for key, entries in _raw_report_method_catalog()['messages'].items(): + assert set(entries) == {'zh', 'en'}, key + fields = [{name for _, name, _, _ in Formatter().parse(entries[language]) if name is not None} + for language in ('zh', 'en')] + assert fields[0] == fields[1], key diff --git a/tests/test_report_methods.py b/tests/test_report_methods.py index 23e831810..3708abdeb 100644 --- a/tests/test_report_methods.py +++ b/tests/test_report_methods.py @@ -183,6 +183,75 @@ def unrelated(out,dataset,rows): assert provider_label('moli-release')==original +def test_method_guide_discloses_retained_success_and_repaired_failure_builds(tmp_path): + from harness.web_fetch.report_methods import render_methods + from harness.web_fetch.report import hero, section, table, cell, link, esc + records = [{'provider': 'sample', 'provider_type': 'fixture', + 'provider_metadata': {'binary_sha256': build}} + for build in ('retained-build', 'retained-build', 'repair-build')] + body = render_methods(tmp_path, records, hero=hero, section=section, + table=table, cell=cell, link=link, esc=esc) + assert 'implementation-scope' in body + assert 'retained-build' in body and 'repair-build' in body + assert '>2<' in body and '>1<' in body + assert '累计成绩不代表某一个版本' in body + single = render_methods(tmp_path, records[:2], hero=hero, section=section, + table=table, cell=cell, link=link, esc=esc) + assert 'implementation-scope' in single + assert 'retained-build' in single and '>2<' in single + unknown = render_methods(tmp_path, [{'provider': 'cloud', 'provider_type': 'fixture'}], + hero=hero, section=section, table=table, cell=cell, link=link, esc=esc) + assert 'unknown' in unknown and '>1<' in unknown + + +def test_bound_external_method_registry_is_scoped_to_its_report(tmp_path): + from harness.web_fetch.methods import with_measured_method_labels, native_browser_engine, browser_method_label, browser_method_registry + @with_measured_method_labels + def render(out,dataset,records): + assert native_browser_engine('external-build') == 'moli' + assert browser_method_label('external-build') == 'External Moli build' + assert browser_method_registry()['external-build']['source_url'] == 'https://example.test/build' + return 'rendered' + dataset={'comparison':{'method_registry':{'external-build':{'engine':'moli','label':'External Moli build','source_url':'https://example.test/build'}}}} + assert render(tmp_path,dataset,[]) == 'rendered' + import json + (tmp_path/'dataset.json').write_text(json.dumps(dataset)) + assert render(tmp_path,{'external-case':{'id':'external-case'}},[]) == 'rendered' + assert native_browser_engine('external-build') is None + + +def test_new_participant_interface_and_access_are_bound_data(tmp_path): + import json + from harness.web_fetch.methods import with_measured_method_labels + from harness.web_fetch.report_interfaces import render_interfaces + (tmp_path/'answer.txt').write_text('A complete saved answer') + (tmp_path/'adoption').mkdir() + contract={'entry':'Future CLI v9 fetch URL','auth':'No account', + 'params':[['url','string','Target page']], + 'fields':[['body','string','Returned text']], + 'request':{'url':'https://example.test/'}, + 'source':'harness/web_fetch/providers/headless_worker.py', + 'notes':'Explicit instance contract.'} + access={'account':'No account','configuration':'Set FUTURE_BINARY.', + 'env_vars':['FUTURE_BINARY'],'links':[['Source','https://example.test/install']]} + dataset={'comparison':{'method_registry':{'future-version':{ + 'engine':'moli','label':'Future participant','report_interface':contract, + 'report_access':access}}}} + rows=[{'provider':'future-version','provider_type':'headless_markdown', + 'provider_metadata':{'reported_version':'future 9.0.0'}, + 'case_id':'example','attempt_id':'future__example', + 'artifact_path':'answer.txt','ok':True}] + @with_measured_method_labels + def render(out, definition, records): + return render_interfaces(out,records) + html=render(tmp_path,dataset,rows) + output=json.loads((tmp_path/'adoption/interfaces/future-version.json').read_text()) + assert output['interface']==contract and output['access']==access + assert 'Future CLI v9 fetch URL' in html + assert 'MOLI_BINARY' not in json.dumps(output) + assert '1.1.5' not in json.dumps(output) + + def test_method_model_selects_direct_english_fields_without_reverse_translation(monkeypatch): records = [{'provider': 'moli', 'provider_type': 'moli_webfetch', 'provider_metadata': {'reported_version': 'moli 9.9.9', 'dump': 'markdown', @@ -199,3 +268,4 @@ def test_method_model_selects_direct_english_fields_without_reverse_translation( observed = method_page_model('moli', 'Moli', records, language='en') assert '1/1 records' in observed['configuration'] assert not re.search(r'[\u4e00-\u9fff]', observed['configuration']) + diff --git a/tests/test_report_presentation.py b/tests/test_report_presentation.py index 8a7e03aa5..c4e0ace19 100644 --- a/tests/test_report_presentation.py +++ b/tests/test_report_presentation.py @@ -1,10 +1,10 @@ import tempfile -import json from pathlib import Path import unittest from harness.web_fetch.report import EXTRA,active_tracks,failure_category,filter_controls,case_attrs,failure_filters,overall_ranking,overview_matched_latency,provider_configuration,review_cell,table,write from harness.web_fetch.report_interfaces import SPECS from harness.web_fetch.methods import method_access + from harness.web_fetch.report_style import STYLE class ReportRevisionTests(unittest.TestCase): @@ -65,6 +65,30 @@ def test_overall_ranking_shows_all_call_latency_without_affecting_pass_order(sel self.assertIn('包含通过与失败',rendered) self.assertLess(rendered.index('slow-high-pass'),rendered.index('fast-low-pass')) self.assertIn('150 ms',rendered) + def test_comparison_defaults_to_pass_rate_including_saved_snapshots(self): + from bs4 import BeautifulSoup + from harness.web_fetch.report import ranking + cases={key:{'id':key,'track':'web','task_kind':'body'} for key in ('a','b')} + records=[{'provider':name,'case_id':key,'ok':True,'elapsed_ms':10, + 'metrics':{'content_pass':key=='a' or name=='z-high'}} + for name in ('a-low','z-high','b-low') for key in cases] + for diagnostic in (False,True): + for render in (lambda:overall_ranking(records,cases,diagnostic=diagnostic), + lambda:ranking(records,cases,'web',diagnostic=diagnostic)): + with self.subTest(diagnostic=diagnostic,render=render): + doc=BeautifulSoup(render(),'html.parser');table=doc.select_one('table.ranking') + column=2 if diagnostic else 3 + self.assertEqual(table.get('data-col'),str(column)) + self.assertEqual(table.get('data-dir'),'desc') + rows=table.select('tbody tr') + self.assertIn('z-high',rows[0].get_text()) + self.assertEqual([row.select('td')[column].get_text() for row in rows],['100.0%','50.0%','50.0%']) + self.assertIsNotNone(rows[0].select('td')[column].select_one('.comparison-winner')) + self.assertFalse(any(row.select('td')[column].select_one('.comparison-winner') for row in rows[1:])) + # A shared maximum has no winner; unavailable metrics remain neutral. + for row in records:row['metrics']['content_pass']=True + doc=BeautifulSoup(overall_ranking(records,cases,diagnostic=True),'html.parser') + self.assertFalse(doc.select('[data-metric="acceptance_2"] .comparison-winner')) def test_ranking_method_names_do_not_wrap_and_tables_remain_scrollable(self): self.assertIn('.table-wrap{overflow:auto',STYLE) self.assertRegex(EXTRA,r'\.ranking td:nth-child\(2\)\{[^}]*min-width:220px[^}]*white-space:nowrap') @@ -98,7 +122,8 @@ def test_pass_no_failure(self): self.assertEqual(failure_category(self.row(metrics={'content_pass':True}),{}),'—') def test_all_interfaces(self): from harness.web_fetch.report_methods import PROFILES - self.assertEqual(set(SPECS),set(PROFILES)) + from harness.web_fetch.report_interfaces import method_spec + for name in PROFILES:self.assertTrue(method_spec(name,None)['entry']) self.assertTrue({'kitesurf','lightpanda','obscura','moli','chromium'} <= set(SPECS)) for s in SPECS.values(): self.assertTrue(s['params'] and s['fields'] and s['request'] and s['source']) @@ -126,7 +151,9 @@ def test_review_label_links_records_without_claiming_fresh_verification(self): self.assertIn('1/1 · 复核记录',row) self.assertNotIn('已核验',row) def test_headless_access_covers_every_method_and_lists_setup_variables(self): - for name in ('kitesurf','lightpanda','obscura','moli','chromium'): + from harness.web_fetch.methods import browser_method_registry + for name in browser_method_registry(): + access=method_access(name,[]) self.assertTrue(access['env_vars']) self.assertTrue(access['links']) @@ -317,13 +344,16 @@ def render(self, records, name='moli'): def test_generic_moli_uses_observed_release_and_task_adapter(self): record={'provider':'moli','provider_type':'moli_webfetch','provider_metadata':{'reported_version':'moli 1.1.15'}} output=self.render([record]);access=output['access'] + self.assertIn('github.com/lexmount/moli',access['links'][0][1]) self.assertIn('/tag/v1.1.15',access['links'][0][1]) self.assertNotIn('1.1.5',str(access)) self.assertEqual(output['interface']['source'],'harness/web_fetch/providers/moli.py') old={'provider':'moli-1.0.6','provider_type':'headless_markdown','provider_metadata':{'reported_version':'moli 1.0.6'}} output=self.render([old],'moli-1.0.6') - self.assertIn('1.0.6',output['access']['links'][0][1]) - self.assertEqual(output['interface'],json.loads(json.dumps(SPECS['moli']))) + self.assertEqual(output['access']['env_vars'], []) + self.assertIn('1.0.6', output['access']['links'][0][1]) + self.assertNotIn('1.1.5', str(output['interface'])) + def test_unknown_or_mixed_generic_versions_do_not_invent_a_release(self): rows=[{'provider':'moli','provider_metadata':{'reported_version':'moli '+v}} for v in ('1.1.14','1.1.15')] for records in ([],rows): @@ -351,3 +381,26 @@ def test_measurement_display_policy_does_not_grant_certification(): assert '不可用于排名' in local_audit_banner(local) local['comparison']['presentation']={'ranking':'saved_task_pass_rate'} assert 'data-ranking-scope="saved-task-pass-rate"' in local_audit_banner(local) + + +def test_input_suite_comparison_uses_declared_membership_and_selected_denominators(): + from harness.web_fetch.report import input_suite_comparison + from bs4 import BeautifulSoup + cases={cid:{'id':cid,'task_kind':'body'} for cid in ['misleading-id','other-id']} + dataset={'comparison':{'input_sources':[ + {'path':'first/dataset.json','case_ids':['other-id','unselected-parent-case']}, + {'path':'second/dataset.json','case_ids':['misleading-id']}], + 'report_suite_labels':{'first/dataset.json':{'zh':'正文','en':'Body'}}}} + rows=[{'case_id':cid,'provider':provider,'ok':True,'metrics':{'content_pass':passed}} + for provider,passed in [('one',True),('two',False)] for cid in cases] + html=input_suite_comparison(dataset,cases,rows); soup=BeautifulSoup(html,'html.parser') + assert '正文' in html and 'n=1' in html and 'n=2' not in html + assert len(soup.select('.comparison-winner'))==2 + assert [cell['data-sort'] for cell in soup.select('tbody tr:first-child td[data-sort]')]==['1.0','1.0'] + for source in dataset['comparison']['input_sources']:source['case_ids']=['misleading-id'] + import pytest + with pytest.raises(ValueError,match='overlapping'): + input_suite_comparison(dataset,cases,rows) + dataset['comparison']['input_sources']=dataset['comparison']['input_sources'][:1] + with pytest.raises(ValueError,match='omits'): + input_suite_comparison(dataset,cases,rows) diff --git a/tests/test_report_templates.py b/tests/test_report_templates.py index bb9e0db41..daede9d2f 100644 --- a/tests/test_report_templates.py +++ b/tests/test_report_templates.py @@ -38,6 +38,21 @@ def diagnostic_example(root): class ReportTemplateTests(unittest.TestCase): + def test_explicit_examples_do_not_load_an_unrelated_measurement_profile(self): + for track in ('webfetch', 'browser-use', 'trajectory-replay'): + with self.subTest(track=track), tempfile.TemporaryDirectory() as temp: + root = Path(temp) + (root/'configs').mkdir() + for name in ('renderer.py', 'build.py', 'test.py'): + (root/name).write_text('# maintained owner') + (root/'default-profile.json').write_text('unavailable historical profile') + (root/'configs/report-templates.json').write_text(json.dumps({ + 'schema_version': 1, 'tracks': {track: { + 'renderer': 'renderer.py', 'entrypoint': 'build.py', + 'tests': 'test.py', 'profile': 'default-profile.json', + 'example': 'https://example.test/s/demo?version=frozen'}}})) + self.assertEqual(resolve_template(track, root=root)['track'], track) + def test_whole_dataset_summary_pools_calls_and_is_required_on_home_and_guide(self): from bs4 import BeautifulSoup with tempfile.TemporaryDirectory() as temp: @@ -379,35 +394,31 @@ def test_diagnostic_explains_failed_checks_and_keeps_review_separate(self): with self.assertRaises(ValueError):check_diagnostic_report(output) target.write_text(original) - def test_default_webfetch_and_wcxb_use_distinct_authoritative_profiles(self): + def test_template_commands_select_external_instance_profiles(self): import shlex - from urllib.parse import urlsplit,parse_qs template=resolve_template('webfetch') - self.assertEqual(template['profile'],'evaluation/current-report.json') + self.assertNotIn('profile',template) + self.assertNotIn('example_profile',template) + for key in ('build','wcxb_build','diagnostic_build'): + self.assertIn('/absolute/instance-profile.json',template[key]) command=shlex.split(template['build']) - self.assertEqual(command[command.index('--profile')+1],template['profile']) self.assertEqual(command[command.index('--output')+1],'reports/webfetch') - self.assertEqual(template['check'],'make report-check') - wcxb=json.loads((ROOT/template['wcxb_profile']).read_text()) - command=shlex.split(template['wcxb_build']) - self.assertEqual(command[command.index('--profile')+1],template['wcxb_profile']) - self.assertEqual(command[command.index('--output')+1],'reports/wcxb') - self.assertEqual(template['example_profile'],template['wcxb_profile']) - self.assertEqual(urlsplit(template['example']).path,'/s/'+wcxb['artifact_hub_site']) - self.assertEqual(parse_qs(urlsplit(template['example']).query)['version'],[wcxb['artifact_hub_expected_version']]) - - def test_optional_variant_profiles_must_exist_within_the_repository(self): + self.assertIn('?version=ver_',template['example']) + + def test_discovery_works_without_measured_instance_files(self): with tempfile.TemporaryDirectory() as temp: root=Path(temp);(root/'configs').mkdir() registry=json.loads((ROOT/'configs/report-templates.json').read_text()) row=registry['tracks']['webfetch'] - for key in ('renderer','entrypoint','profile','tests',*(key for key in row if key.endswith('_profile'))): - path=root/row[key];path.parent.mkdir(parents=True,exist_ok=True);path.write_text('{}') - for value in ('missing-profile.json',str(ROOT/'evaluation/wcxb/current-report.json')): - row['wcxb_profile']=value - (root/'configs/report-templates.json').write_text(json.dumps(registry)) - with self.assertRaisesRegex(ValueError,'profile'): - resolve_template('webfetch',root=root) + for key in ('renderer','entrypoint','tests'): + path=root/row[key];path.parent.mkdir(parents=True,exist_ok=True);path.write_text('') + (root/'configs/report-templates.json').write_text(json.dumps(registry)) + self.assertEqual(resolve_template('webfetch',root=root)['renderer'],row['renderer']) + row['renderer']='../outside.py' + (root/'configs/report-templates.json').write_text(json.dumps(registry)) + with self.assertRaisesRegex(ValueError,'renderer'): + resolve_template('webfetch',root=root) + def test_all_supported_tracks_resolve_existing_owners_and_pinned_examples(self): for track in ('webfetch','trajectory-replay','browser-use'): with self.subTest(track=track): diff --git a/tests/test_reporting_publication.py b/tests/test_reporting_publication.py index f1e8fa41a..2ff14f2c2 100644 --- a/tests/test_reporting_publication.py +++ b/tests/test_reporting_publication.py @@ -1,4 +1,5 @@ """Release archives must contain exactly the validated bytes or preserve the prior release.""" +import hashlib import json import os import zipfile @@ -153,9 +154,17 @@ def test_track_package_uses_frozen_gates_and_preserves_previous_release( for name in ('manifest.json', 'run-config.json', 'attempts.jsonl', 'result-review.json'): (evidence / name).write_text('{}') authority = tmp_path / 'authority' - current = authority / autonomous.PROFILE + current = authority / "evaluation/browser-use/current-report.json" current.parent.mkdir(parents=True) - current.write_text('{}') + selection_hash = hashlib.sha256(b"{}").hexdigest() + current.write_text(json.dumps({ + "task_selection": { + "path": str(current.parent / "webarena-verified-pilot.json"), + "sha256": selection_hash, + }, + "selection_sha256": selection_hash, + })) + (current.parent / "webarena-verified-pilot.json").write_text("{}") (authority / 'research').mkdir() dataset = tmp_path / 'dataset.json' dataset.write_text('{}') diff --git a/tests/test_run_certified_benchmark.py b/tests/test_run_certified_benchmark.py index fefc0448e..bde4e5353 100644 --- a/tests/test_run_certified_benchmark.py +++ b/tests/test_run_certified_benchmark.py @@ -22,6 +22,20 @@ def stage_name(command): class CertifiedBenchmarkTests(unittest.TestCase): + def test_unresolved_selected_rules_fail_before_capture_or_provider_calls(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + dataset = root / 'dataset.json' + dataset.write_text(json.dumps({'cases': [{ + 'id': 'provisional', 'rule_review': {'status': 'unresolved'}}]})) + with patch('harness.web_fetch.certification.run') as invoked: + with self.assertRaises(SystemExit): + main(['--dataset', str(dataset), '--output', str(root / 'run'), + '--work-root', str(root / 'work'), '--providers', 'fixture', + '--case-ids', 'provisional']) + invoked.assert_not_called() + self.assertFalse((root / 'work').exists()) + def test_diagnostic_execution_cannot_be_assembled_or_rendered_as_certified(self): from harness.web_fetch import benchmark_runner as run_benchmark from harness.web_fetch import reference_freeze as freeze_headless_references @@ -90,6 +104,14 @@ def execute(stage, arguments): with self.assertRaisesRegex(ReferenceLifecycleError,'[Dd]iagnostic'): build_report(output,root/'formal-report') self.assertFalse((root/'formal-report').exists()) + # A copied diagnostic run must not become a formal package, + # even after its dataset display label is removed. + from harness.web_fetch.report_package import validate_release + with self.assertRaisesRegex(ReferenceLifecycleError,'[Dd]iagnostic'): + validate_release(output) + (output/'dataset.json').write_text(json.dumps(saved)) + with self.assertRaisesRegex(ReferenceLifecycleError,'[Dd]iagnostic'): + validate_release(output) self.assertEqual(dataset.read_bytes(),original) def test_frozen_contract_execution_needs_no_historical_qualification_files(self): diff --git a/tests/test_runtime_entrypoints.py b/tests/test_runtime_entrypoints.py index 2b71d2899..6e101fe72 100644 --- a/tests/test_runtime_entrypoints.py +++ b/tests/test_runtime_entrypoints.py @@ -1,5 +1,4 @@ from lexbench_webfetch.inputs import definition_path -import hashlib import json import re import shutil @@ -218,38 +217,21 @@ def test_legacy_webfetch_dataset_coverage_matches_expected_methods(self) -> None self.assertEqual(set(providers), expected, track) self.assertEqual(len(providers), 10, track) - def test_current_report_profile_matches_runner_and_canonical_dataset(self) -> None: - profile = json.loads((ROOT / 'evaluation/wcxb/current-report.json').read_text()) - self.assertEqual(profile['comparison_kind'], 'wcxb_frozen_html_to_markdown') - self.assertEqual(profile['dataset_id'], 'wcxb-v1-test-qualified-five-browsers') - self.assertEqual(profile['case_count'], 504) - self.assertEqual(profile['primary_quality_case_count'], 504) - self.assertEqual(profile['sample_manifest'], 'evaluation/public-wcxb-test-qualified.json') - manifest = ROOT / profile['sample_manifest'] - self.assertEqual(profile['sample_manifest_sha256'], hashlib.sha256(manifest.read_bytes()).hexdigest()) - self.assertEqual(len(json.loads(manifest.read_text())['cases']), profile['case_count']) - self.assertEqual( - set(profile['providers']), - {'kitesurf', 'lightpanda', 'obscura', 'moli', 'chromium', 'moli-pr643'}, - ) - self.assertEqual(profile['runs_per_case'], 1) - self.assertEqual(profile['max_failure_retries'], 1) - self.assertEqual( - profile['source_dataset_sha256'], - '080a35a399880bb74ce23f40d9a1008f974500716af183a990f8701ab3e5e7bb', - ) - self.assertEqual(profile['artifact_hub_base_url'], 'https://artifact-site.lexmount.cn') - self.assertEqual(profile['artifact_hub_site'], 'p3sFcZrww_nx') - self.assertRegex(profile['artifact_hub_expected_version'], r'^ver_[0-9a-f]+$') - - def test_publication_receipt_must_target_current_site(self) -> None: - profile = ROOT / 'evaluation/wcxb/current-report.json' - self.assertEqual(current_publication_target(profile), - ('https://artifact-site.lexmount.cn', 'p3sFcZrww_nx')) - for slug in ('uTWj6VaWPYCh', 'UUAhhQMnrGwm', 'UeZE1vfcEOrM'): - with self.assertRaisesRegex(ValueError, 'expected'): - require_publication_site({'slug':slug}, profile, - 'https://artifact-site.lexmount.cn') + def test_publication_receipt_must_target_selected_instance(self) -> None: + with tempfile.TemporaryDirectory() as directory: + profile = Path(directory) / "report.json" + for slug in ("report-one", "report-two"): + profile.write_text(json.dumps({ + "artifact_hub_base_url": "https://reports.example.org", + "artifact_hub_site": slug, + })) + self.assertEqual(current_publication_target(profile), + ("https://reports.example.org", slug)) + self.assertEqual(require_publication_site( + {"slug": slug}, profile, "https://reports.example.org"), slug) + with self.assertRaisesRegex(ValueError, "expected"): + require_publication_site({"slug": "another-report"}, profile, + "https://reports.example.org") def test_publication_version_defaults_to_profile_and_rejects_override(self) -> None: with tempfile.TemporaryDirectory() as directory: diff --git a/tests/test_scoring.py b/tests/test_scoring.py index c94a7ee60..f7eddf2e8 100644 --- a/tests/test_scoring.py +++ b/tests/test_scoring.py @@ -559,12 +559,12 @@ def test_risk_control_requires_all_declared_stable_page_text(self): self.assertTrue(complete["stable_task_pass"]) self.assertTrue(task_passed(True, complete)) - def test_source_title_is_answer_evidence_without_inflating_body_length(self): + def test_source_title_cannot_supply_required_body_facts(self): c=case(required_facts=[{'id':'title','alternatives':['Exact page title']}, {'id':'body','alternatives':['Body fact']}], min_content_chars=20) measured=score_attempt(c,b'Body fact',ok=True,title='Exact page title') - self.assertEqual(measured['fact_coverage'],1) + self.assertEqual(measured['fact_coverage'],.5) self.assertEqual(measured['content_chars'],len('Body fact')) self.assertEqual(measured['title_chars'],len('Exact page title')) self.assertFalse(measured['length_pass']) @@ -624,8 +624,9 @@ def test_dataclass_and_dict_inputs_produce_same_metrics_without_mutation(self): self.assertEqual((mapping, previous, metadata), original) self.assertEqual(first["content_sha256"], hashlib.sha256(b"Alpha\r\nBeta").hexdigest()) self.assertEqual(first["content_chars"], len("Alpha\r\nBeta")) - for key in previous: + for key in previous.keys() - {"raw_file_pass"}: self.assertEqual(first[key], previous[key]) + self.assertNotIn("raw_file_pass", first) def test_text_coverage_is_declared_groups_not_alternatives_or_word_f1(self): c = case(required_facts=[{"id": "a", "alternatives": ["Alpha", "Another Alpha"]}, @@ -864,7 +865,7 @@ def test_old_stable_and_list_decisions_cannot_survive_mode_change(self): self.assertFalse(task_passed(True, measured)) for key in old.keys() - {"raw_file_pass"}: self.assertNotIn(key, measured) - self.assertTrue(measured["raw_file_pass"]) + self.assertNotIn("raw_file_pass", measured) def test_legacy_list_verification_is_shared_not_just_shape(self): c = case(task_kind="structure", source_benchmark=legacy_benchmark()) @@ -1139,7 +1140,7 @@ def concurrent_score(*args, **kwargs): rescore_content.rescore(self.run, case_ids={"web"}) self.assertEqual(path.read_bytes(), concurrent) - def test_both_rescores_retain_external_file_evidence_and_original_deadline(self): + def test_both_rescores_refresh_file_verdict_and_retain_external_identity_and_deadline(self): records, _ = self.live() original = next(r for r in records if r["case_id"] == "file") original["metrics"].update(raw_file_pass=True, raw_file_sha256="independently-verified-file", @@ -1148,6 +1149,7 @@ def test_both_rescores_retain_external_file_evidence_and_original_deadline(self) rescored, summary = runner.rescore_evaluation(run_dir=self.run, dataset_meta=self.dataset, dataset_path=self.path, cases=self.cases) file_row = next(r for r in rescored if r["case_id"] == "file") + original["metrics"]["raw_file_pass"] = False self.assertEqual(file_row, original) self.assertEqual(summary["cases"]["file"]["task_pass_rate"], 0) rescore_content.rescore(self.run, case_ids={"file"}) @@ -1156,6 +1158,30 @@ def test_both_rescores_retain_external_file_evidence_and_original_deadline(self) self.assertEqual(file_row["metrics"], original["metrics"]) self.assertEqual(summarize(script_rows)["cases"]["file"]["task_pass_rate"], 0) + def test_both_rescores_check_current_file_hash_rules_against_retained_bytes(self): + raw = b'\x89PNG\r\n\x1a\n' + b'x' * 32 + self.cases = [case(id='file', task_kind='file', source_verification={ + 'mime': 'image/png', 'file_sha256': hashlib.sha256(raw).hexdigest()})] + self.dataset['cases'] = [asdict(c) for c in self.cases] + self.path.write_text(json.dumps(self.dataset)) + self.provider.fetch.side_effect = lambda c, timeout: ProviderResult( + ok=True, content='file attached', files=[{'data': raw}]) + original, _ = self.live() + self.assertTrue(original[0]['metrics']['raw_file_pass']) + revised = replace(self.cases[0], source_verification={ + 'mime': 'image/png', 'file_sha256': hashlib.sha256(raw + b'other').hexdigest()}) + self.cases = [revised] + self.dataset['cases'] = [asdict(revised)] + rescored, _ = runner.rescore_evaluation(run_dir=self.run, dataset_meta=self.dataset, + dataset_path=self.path, cases=self.cases) + self.assertFalse(rescored[0]['metrics']['raw_file_pass']) + self.assertEqual(rescored[0]['provider_metadata'], original[0]['provider_metadata']) + (self.run / 'dataset.json').write_text(json.dumps(self.dataset)) + rescore_content.rescore(self.run) + row = json.loads((self.run / 'results.jsonl').read_text().strip()) + self.assertFalse(row['metrics']['raw_file_pass']) + self.assertEqual(row['provider_metadata'], original[0]['provider_metadata']) + def test_both_rescores_verify_every_declared_evidence_before_writes(self): records, _ = self.live() self.prepare_script_references() diff --git a/tests/test_scoring_revision.py b/tests/test_scoring_revision.py index 1b01e9bb4..83894ca8b 100644 --- a/tests/test_scoring_revision.py +++ b/tests/test_scoring_revision.py @@ -66,7 +66,7 @@ def test_invalid_history_rejects_before_any_evidence_write(tmp_path: Path, histo canonical, root_cause_category="answer-surface", root_cause="Synthetic correction restores a required fact in the answer surface.", - regression_ids=["answer-title-supplies-required-fact"], + regression_ids=["body-only-word-boundary-answer-title-supplies-required-fact"], ) assert {p.name: p.read_bytes() for p in evidence.iterdir()} == before @@ -78,7 +78,7 @@ def test_valid_history_can_extend_and_retains_prior_receipts(tmp_path: Path): authority = { "root_cause_category": "answer-surface", "root_cause": "Synthetic correction restores a required fact in the answer surface.", - "regression_ids": ["answer-title-supplies-required-fact"], + "regression_ids": ["body-only-word-boundary-answer-title-supplies-required-fact"], } first = revise(evidence, canonical, **authority) assert first["task_decision_changes"] == [ @@ -131,7 +131,7 @@ def test_reader_prompt_can_follow_scoring_contract_without_changing_saved_call(t canonical, root_cause_category="answer-surface", root_cause="The reader-facing scope described obsolete answer tokens that providers never receive.", - regression_ids=["answer-title-supplies-required-fact"], + regression_ids=["body-only-word-boundary-answer-title-supplies-required-fact"], ) assert receipt["provider_calls"] == 0 @@ -160,7 +160,7 @@ def test_reader_prompt_revision_cannot_change_provider_url(tmp_path: Path): canonical, root_cause_category="answer-surface", root_cause="The reader-facing scope changed but the physical provider URL must remain frozen.", - regression_ids=["answer-title-supplies-required-fact"], + regression_ids=["body-only-word-boundary-answer-title-supplies-required-fact"], ) assert {path.name: path.read_bytes() for path in evidence.iterdir()} == before @@ -179,7 +179,7 @@ def test_scorer_drift_and_task_rule_change_are_rejected_atomically(tmp_path: Pat canonical, root_cause_category="answer-surface", root_cause="A shared answer-surface correction changed the scorer implementation.", - regression_ids=["answer-title-supplies-required-fact"], + regression_ids=["body-only-word-boundary-answer-title-supplies-required-fact"], ) assert {path.name: path.read_bytes() for path in evidence.iterdir()} == before @@ -194,7 +194,7 @@ def test_scorer_drift_and_task_rule_change_can_be_recorded_in_two_steps(tmp_path authority = { "root_cause_category": "answer-surface", "root_cause": "A shared answer-surface correction changed the scorer implementation.", - "regression_ids": ["answer-title-supplies-required-fact"], + "regression_ids": ["body-only-word-boundary-answer-title-supplies-required-fact"], } scorer_receipt = revise(evidence, evidence / "dataset.json", **authority) @@ -243,7 +243,7 @@ def test_scorer_only_revision_accepts_an_assembled_mixed_source_cohort(tmp_path: evidence / "dataset.json", root_cause_category="answer-surface", root_cause="An assembled cohort retains outputs scored by two older scorer builds.", - regression_ids=["answer-title-supplies-required-fact"], + regression_ids=["body-only-word-boundary-answer-title-supplies-required-fact"], ) assert receipt["input_scoring_source_sha256s"] == ["0" * 64, "1" * 64] @@ -268,7 +268,7 @@ def test_scoring_validation_preserves_complete_saved_attempt_bytes(tmp_path, fai receipt = revise(evidence, evidence / 'dataset.json', validate_only=True, root_cause_category='answer-surface', root_cause='Validate the saved scoring without rewriting a physical-call record.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) assert receipt['validation_only'] is True assert receipt['task_decision_changes'] == [] assert receipt['input_results_sha256'] == receipt['output_results_sha256'] @@ -289,7 +289,7 @@ def test_scoring_validation_rejects_changed_verdict_or_requirements_atomically(t revise(evidence, canonical, validate_only=True, root_cause_category='answer-surface', root_cause='Validation rejects changed scores and requirements without writing evidence.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) assert {p.name:p.read_bytes() for p in evidence.iterdir()} == before @@ -298,7 +298,7 @@ def test_report_assets_can_update_together_without_changing_scored_calls(tmp_pat evidence, canonical, _ = revision_fixture(tmp_path, b'[]') revise(evidence, canonical, root_cause_category='answer-surface', root_cause='Synthetic required-answer correction has an executable acceptance control.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) calls = (evidence / 'results.jsonl').read_bytes() content = evidence / 'report-assets' / 'browser-interpretation.json' content.parent.mkdir() @@ -328,7 +328,7 @@ def test_report_asset_update_rejects_unbound_provenance_before_writing(tmp_path, evidence, canonical, _ = revision_fixture(tmp_path, b'[]') revise(evidence, canonical, root_cause_category='answer-surface', root_cause='Synthetic required-answer correction has an executable acceptance control.', - regression_ids=['answer-title-supplies-required-fact']) + regression_ids=['body-only-word-boundary-answer-title-supplies-required-fact']) content = evidence / 'article.json' content.write_text('{}') implementation = evidence / 'implementation-provenance.json' diff --git a/tests/test_source_language_policy.py b/tests/test_source_language_policy.py new file mode 100644 index 000000000..46b897a79 --- /dev/null +++ b/tests/test_source_language_policy.py @@ -0,0 +1,46 @@ +"""Implementation language and localized content have different boundaries.""" +from pathlib import Path + +import pytest + +from harness.reporting.source_policy import check_source_languages + + +def test_repository_implementation_language(): + assert check_source_languages(Path(__file__).resolve().parents[1]) == [] + + +def test_comments_identifiers_and_docstrings_fail_but_source_text_survives(tmp_path): + source = tmp_path / 'harness' + source.mkdir() + path = source / 'adapter.py' + path.write_text('"""维护说明"""\n# 实现注释\n结果 = "原文引述"\n', encoding='utf-8') + findings = check_source_languages(tmp_path) + assert len(findings) == 3 + path.write_text('"""Read source evidence."""\n# Preserve the original text.\nvalue = "原文引述"\n') + assert check_source_languages(tmp_path) == [] + + +@pytest.mark.parametrize("relative", [ + "scripts/moli_report/app.js", "harness/trajectory_replay/interpretation.py", + "harness/web_fetch/methods.py", "harness/web_fetch/report_interfaces.py", +]) +def test_catalog_renderer_rejects_inline_messages_without_banning_chinese_catalogs(tmp_path, relative): + path = tmp_path / relative + path.parent.mkdir(parents=True) + prefix = 'const ' if path.suffix == '.js' else '' + path.write_text(prefix + 'label = "结果";') + assert len(check_source_languages(tmp_path)) == 1 + path.write_text(prefix + 'label = messages.result;') + path.with_name('messages.json').write_text('{"zh": {"result": "结果"}}') + assert check_source_languages(tmp_path) == [] + + +def test_report_instances_stay_external_but_task_definitions_remain_versioned(tmp_path): + inputs = tmp_path / "evaluation/example" + inputs.mkdir(parents=True) + (inputs / "tasks.json").write_text('{"cases": []}') + assert check_source_languages(tmp_path) == [] + (inputs / "current-report.json").write_text('{}') + assert check_source_languages(tmp_path) == [ + "evaluation/example/current-report.json: keep report instances outside Git"] diff --git a/tests/test_storage.py b/tests/test_storage.py index cb56e6737..6cbda41b5 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -19,9 +19,12 @@ class StorageTests(unittest.TestCase): def test_legacy_profile_validator_rejects_wcxb_with_track_specific_error(self): - root = Path(__file__).resolve().parents[1] - with self.assertRaisesRegex(ValueError, 'WCXB evidence uses the certified cohort validator'): - require_current_report_profile(root, root / 'evaluation/wcxb/current-report.json') + with tempfile.TemporaryDirectory() as folder: + root = Path(folder) + profile = root / 'profile.json' + profile.write_text(json.dumps({'comparison_kind': 'wcxb_frozen_html_to_markdown'})) + with self.assertRaisesRegex(ValueError, 'WCXB evidence uses the certified cohort validator'): + require_current_report_profile(root, profile) def test_explicit_data_home_and_evidence_override(self): with patch.dict(os.environ, {"BROWSER_EVAL_DATA_HOME": "/tmp/browser-data"}, clear=False): diff --git a/tests/test_three_cohort_review_followup.py b/tests/test_three_cohort_review_followup.py index c9aec64a7..9052b4e48 100644 --- a/tests/test_three_cohort_review_followup.py +++ b/tests/test_three_cohort_review_followup.py @@ -1,7 +1,5 @@ """Scorer controls added after the historical dynamic-audit test inventory froze.""" -from unittest.mock import patch - import pytest from lexbench_webfetch.scoring import score_attempt @@ -39,18 +37,15 @@ def test_metadata_title_length_cannot_expose_hidden_html(length): assert direct['stable_anchor_results']==scored['stable_anchor_results'] -def test_html_body_text_is_normalized_once_without_losing_title(): - from lexbench_webfetch.dynamic_lists import _scoped_html +def test_html_body_text_preserves_visible_words_and_title(): rules={'version':1,'schema':'body_text','minimum_records':0, 'stable_text_anchors':[{'id':'body','alternatives':['Required body']}]} html='

Required body

' case={'id':'article','url':'https://example.com/article','track':'web','task_kind':'body','acceptance_contract':rules} - with patch('lexbench_webfetch.dynamic_lists._scoped_html',wraps=_scoped_html) as parser: - scored=score_attempt(case,html.encode(),ok=True,title='Page heading') + scored=score_attempt(case,html.encode(),ok=True,title='Page heading') assert scored['stable_task_pass'] is True assert scored['blocked_page'] is False assert scored['title_chars']==len('Page heading') - assert parser.call_count==1 @pytest.mark.parametrize('tag', ['strong', 'code', 'em', 'a', 'span']) @@ -145,13 +140,13 @@ def test_legacy_page_text_rules_cannot_take_evidence_from_metadata(rule): assert score_attempt(case,content.encode(),ok=True,title=title)['content_pass'] is expected -def test_explicit_title_fact_and_body_scope_are_independent_requirements(): +def test_required_body_facts_and_body_scope_cannot_be_supplied_by_title_metadata(): from lexbench_webfetch.body_coverage import body_requirements text='Required body paragraph explains independent visible source content and complete record ownership.' case={'url':'https://example.test/article','task_kind':'body', 'required_facts':[{'id':'title','alternatives':['Exact page title']}], 'body_requirements':body_requirements(text)} - for content,title,expected in [(text,'Exact page title',True), - (text,'Wrong title',False), - ('Other text','Exact page title',False)]: + for content,title,expected in [(text+'\nExact page title','Wrong title',True), + (text,'Exact page title',False), + ('Exact page title','Exact page title',False)]: assert score_attempt(case,content.encode(),ok=True,title=title)['content_pass'] is expected diff --git a/tests/test_wcxb_contract.py b/tests/test_wcxb_contract.py index 15ae1b99b..a489a0942 100644 --- a/tests/test_wcxb_contract.py +++ b/tests/test_wcxb_contract.py @@ -25,22 +25,17 @@ def test_conditional_latency_is_unavailable_without_delivered_pages(self): self.assertEqual(format_wcxb_conditional_latency(1234.2), '1,234 ms') def test_wcxb_participants_do_not_inherit_webmainbench_version_rows(self): - profile = json.loads((ROOT / 'evaluation/wcxb/current-report.json').read_text()) - self.assertEqual(tuple(profile['providers']), WCXB_PROVIDERS) self.assertIn('moli-1.1.7', PROVIDERS) self.assertNotIn('moli-1.1.7', WCXB_PROVIDERS) - def test_frozen_manifest_is_unique_stratified_and_profile_bound(self): + def test_frozen_manifest_is_unique_and_stratified(self): raw = (ROOT / 'evaluation/public-wcxb-test-qualified.json').read_bytes() sample = json.loads(raw) - profile = json.loads((ROOT / 'evaluation/wcxb/current-report.json').read_text()) self.assertEqual(sample['size'], 504) self.assertEqual(len(sample['cases']), 504) self.assertEqual(len({r['file_id'] for r in sample['cases']}), 504) self.assertEqual(sum(sample['distribution']['sample'].values()), 504) self.assertEqual(set(sample['distribution']['sample']), set(sample['distribution']['parent'])) - self.assertEqual(hashlib.sha256(raw).hexdigest(), profile['sample_manifest_sha256']) - self.assertEqual(sample['source_sha256'], profile['source_dataset_sha256']) self.assertEqual(sample['primary_quality_case_count'], 504) def test_full_test_exclusions_follow_source_content_only(self): @@ -134,3 +129,49 @@ def write(calls, binary='candidate'): if __name__ == '__main__': unittest.main() + + +# Exercise admission through the public run path; no provider call is issued. +def test_wcxb_execution_reads_versions_only_from_selected_profile(tmp_path, monkeypatch): + from types import SimpleNamespace + import pytest + from harness.web_fetch import webmainbench_runner as runner + from tests.wcxb_fixture import create_cohort + + fixture = create_cohort(tmp_path / 'inputs', providers=('moli', 'chromium')) + profile = json.loads(fixture['profile_path'].read_text()) + args = SimpleNamespace( + output=tmp_path / 'new-run', case_ids_file=fixture['sample_manifest'], + benchmark='wcxb', provider='moli', dataset=fixture['source'], + profile=fixture['profile_path'], limit=None, env_file=tmp_path / '.env', + config=tmp_path / 'providers.json', preserve_original_origin=False, + ) + observed = [] + class Method: + def validate(self): + return [] + def implementation_identity(self): + observed.append('identity') + return {'reported_version': 'moli 2.3.4'} + monkeypatch.setattr(runner, 'load_runtime_env', lambda _: None) + monkeypatch.setattr(runner, 'load_provider_config', lambda _: {'moli': {}}) + monkeypatch.setattr(runner, 'build_provider', lambda *_: Method()) + class Admitted(Exception): + pass + def checkpoint(*_): + raise Admitted + monkeypatch.setattr(runner, 'write_json', checkpoint) + for declared, error in ( + ({'moli': '2.3.4'}, 'every participant'), + ({'moli': '2.0.0', 'chromium': '150.0'}, 'expected version 2.0.0'), + ): + profile['expected_versions'] = declared + args.profile.write_text(json.dumps(profile)) + with pytest.raises(ValueError, match=error): + runner.run(args) + assert not args.output.exists() + profile['expected_versions'] = {'moli': '2.3.4', 'chromium': '150.0'} + args.profile.write_text(json.dumps(profile)) + with pytest.raises(Admitted): + runner.run(args) + assert observed == ['identity', 'identity'] diff --git a/tests/test_wcxb_latency_collection.py b/tests/test_wcxb_latency_collection.py index 6d2a81c98..f8187c5b7 100644 --- a/tests/test_wcxb_latency_collection.py +++ b/tests/test_wcxb_latency_collection.py @@ -217,8 +217,8 @@ def test_collector_cli_preserves_repository_default_and_supports_caller_default( monkeypatch.setattr(timing, 'collect', lambda **kw: captured.append(kw) or {}) repository = tmp_path / 'repository' if repository_default else None timing.main(['--dataset', 'dataset', '--manifest', 'manifest.json', - '--release', 'released', '--candidate', 'candidate', '--output', 'new'], root=repository) - assert captured[0]['profile'] == (repository or tmp_path) / 'evaluation/wcxb/current-report.json' + '--release', 'released', '--candidate', 'candidate', '--output', 'new', '--profile', str(tmp_path/'selected.json')], root=repository) + assert captured[0]['profile'] == tmp_path / 'selected.json' @pytest.mark.parametrize('options', [[], ['--profile', '/explicit/profile.json'], ['--profile=/explicit/profile.json']]) @@ -227,4 +227,4 @@ def test_existing_certification_command_keeps_its_repository_authority(monkeypat captured = [] monkeypatch.setattr(timing, 'certify', lambda argv: captured.append(argv)) certification.main(['wcxb-latency', *options]) - assert captured == [options or ['--profile', str(certification.ROOT / 'evaluation/wcxb/current-report.json')]] + assert captured == [options] diff --git a/tests/test_wcxb_participant_registry.py b/tests/test_wcxb_participant_registry.py new file mode 100644 index 000000000..96489fa1e --- /dev/null +++ b/tests/test_wcxb_participant_registry.py @@ -0,0 +1,91 @@ +"""A report instance may use participant IDs absent from historical cohorts.""" +from harness.web_fetch.report import generate_wcxb +from tests.wcxb_fixture import create_report_inputs + + +def test_wcxb_renderer_accepts_entirely_new_participant_ids(tmp_path): + root, rows, summary, ground_truth, identities, profile, content = create_report_inputs(tmp_path) + names = {name: 'participant-' + str(i) for i, name in enumerate(summary['providers'])} + for row in rows: + row['provider'] = names[row['provider']] + summary['providers'] = {names[name]: values for name, values in summary['providers'].items()} + identities = {names[name]: values for name, values in identities.items()} + profile['providers'] = [names[name] for name in profile['providers']] + profile['versions'] = {names[name]: value for name, value in profile['versions'].items()} + profile['paired_comparison'] = {'baseline': names['moli'], 'candidate': names['moli-pr643']} + for name, replacement in names.items(): + (root / 'artifacts' / name).rename(root / 'artifacts' / replacement) + generate_wcxb(root, rows, summary, ground_truth, identities, profile, content, include_english=True) + for prefix in ('', 'en/'): + home = (root / prefix / 'index.html').read_text() + assert all('providers/' + name + '.html' in home for name in names.values()) + assert 'data-baseline="' + names['moli'] + '"' in home + assert 'data-candidate="' + names['moli-pr643'] + '"' in home + assert 'Only Kitesurf' not in (root / prefix / 'accounts/index.html').read_text() + + +def test_public_wcxb_build_check_package_accept_external_nonhistorical_cohort(tmp_path): + import hashlib + import json + import subprocess + import sys + from pathlib import Path + from harness.web_fetch.wcxb_scoring import score + from tests.wcxb_fixture import create_cohort, write_json + + repository = Path(__file__).resolve().parents[1] + cohort = create_cohort(tmp_path / 'cohort', providers=('alpha', 'beta', 'gamma')) + summary = score(**cohort) + evidence = cohort['evidence_root'] + profile = json.loads(cohort['profile_path'].read_bytes()) + review_path = evidence / 'report-assets/research/wcxb-representative-review.json' + write_json(review_path, {'scope': 'targeted_representative_anomaly_review', + 'case_count': 0, 'result_count': 0, 'total_results': 6, 'unreviewed_results': 6, 'cases': []}) + profile.update(source_repository='https://example.invalid/benchmark', source_commit='a' * 40, + versions={name: 'synthetic' for name in profile['providers']}, + report_assets={'research/wcxb-representative-review.json': + hashlib.sha256(review_path.read_bytes()).hexdigest()}) + anchors = ('guide-results', 'guide-score', 'guide-cases', 'guide-methods', + 'guide-timing', 'guide-conclusion') + rows = [] + for name, values in summary['providers'].items(): + rows.append('' + name + '' + '' + + format(values['quality_f1'] * 100, '.2f') + '%' + '' + + format(values['mean_latency_ms'], ',.0f') + ' ms') + table = '' + ''.join(rows) + '
MethodF1Latency
' + body = '' + for anchor in anchors: + detail = table if anchor in ('guide-results', 'guide-methods') else '

Frozen inputs and retained outputs are compared by the same official evaluator.

' + if anchor == 'guide-cases': + detail += 'Inspect source, reference and outputs' + if anchor == 'guide-conclusion': + detail = ('

The complete population contains two frozen pages and three methods. ' + 'One method has a retained request failure, which stays in the score and latency population. ' + 'The other methods tie on these synthetic inputs. These fixtures test evidence handling; ' + 'they do not measure browser capability or support an adoption recommendation.

') + body += '

' + anchor + '

' + detail + '
' + content_path = evidence / 'report-assets/report-content.json' + write_json(content_path, {'schema_version': 1, 'guide': {'zh': body, 'en': body}}) + profile_path = tmp_path / 'instance/profile.json' + write_json(profile_path, profile) + binding = {'schema_version': 1, 'relative_path': 'report-content.json', + 'sha256': hashlib.sha256(content_path.read_bytes()).hexdigest(), + 'evidence': {'wcxb-score-summary.json': hashlib.sha256((evidence / 'wcxb-score-summary.json').read_bytes()).hexdigest()}, + 'authority': {'configs/report-templates.json': hashlib.sha256((repository / 'configs/report-templates.json').read_bytes()).hexdigest()}, + 'rendered_sha256': {'guide': {lang: hashlib.sha256(body.encode()).hexdigest() for lang in ('zh', 'en')}}} + write_json(profile_path.with_name('report-content-binding.json'), binding) + output = tmp_path / 'report' + commands = [ + ['scripts/build_report.py', '--profile', str(profile_path), '--evidence', str(evidence), + '--wcxb-source', str(cohort['source']), '--wcxb-sample', str(cohort['sample_manifest']), + '--output', str(output), '--language', 'en'], + ['scripts/check_report.py', str(output), '--profile', str(profile_path)], + ['scripts/publish_wcxb_artifact_site.py', '--package-only', '--source', str(output), + '--profile', str(profile_path), '--work', str(tmp_path / 'package')], + ] + for command in commands: + result = subprocess.run([sys.executable, *command], cwd=repository, capture_output=True, text=True) + assert result.returncode == 0, result.stdout + result.stderr + assert (tmp_path / 'package/wcxb-current.zip').is_file() diff --git a/tests/test_wcxb_publication.py b/tests/test_wcxb_publication.py index 681f2308a..9d05c3d12 100644 --- a/tests/test_wcxb_publication.py +++ b/tests/test_wcxb_publication.py @@ -202,3 +202,15 @@ def interrupt(path, payload): journals = list(kwargs['work'].glob('wcxb-update-*.json')) assert len(journals) == 1 assert json.loads(journals[0].read_bytes())['verified'] is True + + +def test_package_only_cli_never_publishes_or_loads_credentials(report, monkeypatch, capsys): + paths, _profile, checks = report + monkeypatch.setattr(release, 'publish', lambda **_kwargs: pytest.fail('package-only published')) + release.main(['--package-only', '--profile', str(_profile), '--authority-root', str(paths['authority_root']), + '--source', str(paths['source']), '--work', str(paths['work'])]) + result = json.loads(capsys.readouterr().out) + assert result['files'] == 2 + assert (paths['work'] / 'wcxb-current.zip').is_file() + assert (paths['work'] / 'wcxb-package.json').is_file() + assert [item[0] for item in checks] == ['semantic', 'links'] diff --git a/tests/test_wcxb_report_check.py b/tests/test_wcxb_report_check.py index def1b9841..5282fa8dc 100644 --- a/tests/test_wcxb_report_check.py +++ b/tests/test_wcxb_report_check.py @@ -51,7 +51,28 @@ def test_localized_number_spacing_does_not_change_measurements(): require_bilingual_shape(zh, wrong, "index.html") -def test_guide_checks_bound_content_and_current_methods_without_fixed_cases(): +def test_bilingual_navigation_keeps_filters_and_normalizes_language_selection(): + zh, en, broken = ReportShape(), ReportShape(), ReportShape() + zh.feed('中文') + en.feed('English') + broken.feed('English') + require_bilingual_shape(zh, en, "cases/example.html") + with pytest.raises(ValueError, match="bilingual"): + require_bilingual_shape(zh, broken, "cases/example.html") + + +def test_primary_language_entry_script_does_not_change_report_content_shape(): + zh, en, broken = ReportShape(), ReportShape(), ReportShape() + zh.feed('

100

') + en.feed('

100

') + broken.feed('

100

') + require_bilingual_shape(zh, en, "index.html") + with pytest.raises(ValueError, match="bilingual"): + require_bilingual_shape(zh, broken, "index.html") + + +@pytest.mark.parametrize("separate_example", [False, True]) +def test_guide_checks_bound_content_and_current_methods_without_fixed_cases(separate_example): from bs4 import BeautifulSoup from harness.web_fetch.wcxb_report_check import require_guide_interpretation @@ -67,14 +88,21 @@ def test_guide_checks_bound_content_and_current_methods_without_fixed_cases(): html += '
alpha
beta
' html += '
CallsCertificate
' html += '
' + 'This is a synthetic bound finding with independent evidence. ' * 5 + '
' + if separate_example: + html = html.replace('
Examples
"task_routes": 1, "result_routes": 1, } + + +def test_bilingual_compaction_preserves_language_links_and_evidence(tmp_path): + import pytest + source = tmp_path / 'source' + for prefix in ('', 'en/'): + root = source / prefix + (root / 'task').mkdir(parents=True) + (root / 'result').mkdir() + (root / 'index.html').write_text('Result') + (root / 'task/1.html').write_text('Result') + switch = '../../result/browser-1.html' if prefix else '../en/result/browser-1.html' + asset = '../../assets/report.css' if prefix else '../assets/report.css' + (root / 'result/browser-1.html').write_text( + f'Switch' + 'Task') + (source / 'assets').mkdir() + (source / 'assets/app.js').write_text('window.ready=true') + (source / 'assets/report.css').write_text('body{color:black}') + (source / 'data/evidence').mkdir(parents=True) + (source / 'data/trajectory').mkdir() + (source / 'data/evidence/browser-1.json').write_text('{"task_id":1,"score":0}') + (source / 'data/trajectory/browser-1.json').write_text('{"actions":["click"]}') + (source / 'report-manifest.json').write_text(json.dumps({ + 'languages': ['zh', 'en'], 'task_count': 1, 'logical_results': 1})) + output = tmp_path / 'compact' + compact_report(source, output) + validate_compact_report(output, max_files=200, source=source) + english = json.loads((output / 'en/pages/result-01.json').read_text())['browser-1'] + chinese = json.loads((output / 'pages/result-01.json').read_text())['browser-1'] + assert 'href="../result.html?key=browser-1"' in english + assert 'href="../assets/report.css"' in english + assert 'href="en/result.html?key=browser-1"' in chinese + assert 'Missing task and evidence

'})) + with pytest.raises(ValueError, match='pages differ'): + validate_compact_report(output, max_files=200, source=source) diff --git a/tests/trajectory_replay/test_webarena_dataset.py b/tests/trajectory_replay/test_webarena_dataset.py index 32318c6ba..f69e62603 100644 --- a/tests/trajectory_replay/test_webarena_dataset.py +++ b/tests/trajectory_replay/test_webarena_dataset.py @@ -20,14 +20,11 @@ ) -def test_repository_dataset_covers_the_current_frozen_selection() -> None: +def test_committed_trajectories_match_their_task_contract() -> None: root = Path(__file__).resolve().parents[2] - profile = json.loads( - (root / "evaluation/trajectory-replay/webarena-verified/current-run.json").read_text( - encoding="utf-8" - ) + selection = json.loads( + (root / "evaluation/browser-use/webarena-verified-pilot.json").read_text() ) - selection = json.loads((root / profile["task_selection"]["path"]).read_text(encoding="utf-8")) dataset = json.loads( (root / "evaluation/trajectory-replay/webarena-verified/trajectories.json").read_text( encoding="utf-8" @@ -36,9 +33,15 @@ def test_repository_dataset_covers_the_current_frozen_selection() -> None: expected_ids = {int(item["task_id"]) for item in selection["tasks"]} actual_ids = {int(item["task_id"]) for item in dataset["trajectories"]} - required = int(profile["qualification"]["required_task_count"]) - - assert required == 90 + required = len(expected_ids) + + assert required > 0 + assert ( + dataset["selection_sha256"] + == hashlib.sha256( + (root / "evaluation/browser-use/webarena-verified-pilot.json").read_bytes() + ).hexdigest() + ) assert expected_ids == actual_ids assert dataset["counts"] == { "verified_tasks": required, @@ -46,7 +49,7 @@ def test_repository_dataset_covers_the_current_frozen_selection() -> None: } for item in dataset["trajectories"]: verification = item["verification"] - assert verification["browsers"] == [profile["qualification"]["reference_engine"]] + assert verification["browsers"] == [dataset["promotion_policy"]["qualification_browser"]] assert verification["process_status"] == "pass" assert verification["evaluator_status"] == "pass" assert verification["model_calls"] == 0 diff --git a/tests/trajectory_replay/test_webarena_publish.py b/tests/trajectory_replay/test_webarena_publish.py index 92728af07..3b2f664b7 100644 --- a/tests/trajectory_replay/test_webarena_publish.py +++ b/tests/trajectory_replay/test_webarena_publish.py @@ -499,7 +499,7 @@ def test_package_binds_report_evidence_and_release_gate(monkeypatch, tmp_path: P monkeypatch.setattr( packager, "validate_release", - lambda _report, _receipt: {"html_pages": 1, "checked_links": 0}, + lambda _report, _receipt, **_kwargs: {"html_pages": 1, "checked_links": 0}, ) result = packager.package(report, evidence, output, tmp_path / "visual.json") @@ -534,7 +534,7 @@ def test_package_reruns_private_preview_gate_for_old_receipt( "failures": [], }), encoding="utf-8") calls = [] - monkeypatch.setattr(packager, "check_report", lambda _report: {"html_pages": 1}) + monkeypatch.setattr(packager, "check_report", lambda _report, **_kwargs: {"html_pages": 1}) monkeypatch.setattr(packager, "check_visual", lambda *_args: calls.append("private preview")) packager.validate_release(report, receipt) @@ -558,7 +558,7 @@ def test_package_rejects_diagnostic_appendix_and_mixed_dataset( monkeypatch.setattr(packager, "PROFILE", profile) monkeypatch.setattr(packager, "REVIEW", review) monkeypatch.setattr(packager, "ATTESTATION", tmp_path / "attestation.json") - monkeypatch.setattr(packager, "validate_release", lambda *_args: {}) + monkeypatch.setattr(packager, "validate_release", lambda *_args, **_kwargs: {}) manifest = {"dataset_sha256": _sha256(dataset), "results_sha256": _sha256(evidence), "diagnostic_closure_sha256": "a" * 64} (report / "report-manifest.json").write_text(json.dumps(manifest)) diff --git a/tests/trajectory_replay/test_webarena_report.py b/tests/trajectory_replay/test_webarena_report.py index 1375d871f..6105f1345 100644 --- a/tests/trajectory_replay/test_webarena_report.py +++ b/tests/trajectory_replay/test_webarena_report.py @@ -167,14 +167,14 @@ def test_complete_three_method_report_builds_one_unified_matrix( assert manifest["browser_count"] == 2 assert manifest["method_count"] == len(methods) assert manifest["logical_results"] == len(methods) - assert manifest["routes"] == 20 + 2 * extra_methods + assert manifest["routes"] == 24 + 2 * extra_methods assert "36/36" not in (output / "index.html").read_text() assert "fixes.html" not in (output / "index.html").read_text() chinese_home = (output / "index.html").read_text() english_home = (output / "en/index.html").read_text() - assert "Playwright 重放器与浏览器的组合" in chinese_home - assert "Playwright-adapter–browser configurations" in english_home - assert "没有调用 CowAgent BrowserTool" in chinese_home + assert "历史配置没有声明执行后端" in chinese_home + assert "historical profile does not declare an execution backend" in english_home + assert "没有 Agent 决策或模型调用" in chinese_home assert "本轮没有记录进程级 CPU、内存和网络采样" in chinese_home assert "CPU 时间" not in chinese_home assert "平均内存" not in chinese_home @@ -205,7 +205,7 @@ def test_complete_three_method_report_builds_one_unified_matrix( check_report(output) environment_path.write_text(original_environment) assert not (output / "diagnostics.html").exists() - assert check_report(output)["html_pages"] == 20 + 2 * extra_methods + assert check_report(output)["html_pages"] == 24 + 2 * extra_methods # Byte-identical links must not bypass the publication boundary. linked_report = tmp_path / "linked-report" linked_report.symlink_to(output, target_is_directory=True) @@ -220,9 +220,16 @@ def test_complete_three_method_report_builds_one_unified_matrix( check_report(output) artifact.unlink() retained.rename(artifact) - assert check_report(output)["html_pages"] == 20 + 2 * extra_methods + assert check_report(output)["html_pages"] == 24 + 2 * extra_methods home = output / "index.html" original_home = home.read_text() + for language_prefix in ("", "en/"): + for route in ("index.html", "environment.html", "results.html"): + rendered = (output / language_prefix / route).read_text() + assert "All six methods" not in rendered + assert "六种方法" not in rendered + assert "Playwright" not in rendered + assert "execution backend" in (output / "en/index.html").read_text() home.write_text(original_home.replace(profile["measurement_notes"]["zh"], "")) with pytest.raises(ValueError, match="measurement context"): check_report(output) @@ -234,7 +241,7 @@ def test_complete_three_method_report_builds_one_unified_matrix( ) assert manifest["languages"] == ["zh"] assert manifest["english_status"] == "not_generated" - assert manifest["routes"] == 11 + extra_methods + assert manifest["routes"] == 13 + extra_methods english_notice = (output / "en/index.html").read_text() assert '英文报告未生成' in english_notice assert 'new URL("en/",b.href)' in english_notice @@ -244,7 +251,7 @@ def test_complete_three_method_report_builds_one_unified_matrix( 'class="language" href="../en/index.html"' in (output / "result/chromium-29.html").read_text() ) - assert check_report(output)["html_pages"] == 11 + extra_methods + assert check_report(output)["html_pages"] == 13 + extra_methods manifest_path = output / "report-manifest.json" changed = json.loads(manifest_path.read_text()) changed["browser_count"] += 1 @@ -1394,3 +1401,15 @@ def test_missing_submission_is_located_at_trigger_without_calling_it_a_failed_ac assert "提交请求未出现" not in zh and "未出现的请求" not in zh assert "Network acceptance not satisfied" in en and "Required network match" in en assert "request absent" not in en and "Missing required request" not in en + + +def test_supplemental_requests_keep_unavailable_url_and_observed_version(): + from harness.trajectory_replay.webarena_report import _supplemental_diagnostic_html + diagnostic = {'observations': [{'method': 'release', 'version': 'OtherBrowser 9', + 'observations': [{'delay': 0, 'state': {'httpReads': [ + {'status': 503, 'error': 'Read failed before a URL was recorded'}, + {'url': '/observed-endpoint', 'status': 200}]}}]}]} + html = _supplemental_diagnostic_html(diagnostic, 'en') + assert 'OtherBrowser 9' in html and 'Moli 1.1.8' not in html + assert 'Not recorded' in html and '/observed-endpoint' in html + assert 'Read failed before a URL was recorded' in html diff --git a/tests/trajectory_replay/test_webarena_runtime_source.py b/tests/trajectory_replay/test_webarena_runtime_source.py index 79a7b7eb9..9aa99d3ce 100644 --- a/tests/trajectory_replay/test_webarena_runtime_source.py +++ b/tests/trajectory_replay/test_webarena_runtime_source.py @@ -22,9 +22,7 @@ class _Distribution: def __init__(self, root: Path, direct_url: object, *, owns_package: bool = True): self.root = root self.direct_url = direct_url - self.files = ( - [Path("webarena_verified/__init__.py")] if owns_package else [Path("other.py")] - ) + self.files = [Path("webarena_verified/__init__.py")] if owns_package else [Path("other.py")] def read_text(self, name: str): assert name == "direct_url.json" @@ -78,7 +76,11 @@ def test_accepts_exact_lexmount_fork_revision_and_owned_module(monkeypatch, tmp_ def test_rejects_servicenow_repository(monkeypatch, tmp_path): - _install(monkeypatch, tmp_path, _direct_url(url="https://github.com/ServiceNow/webarena-verified.git")) + _install( + monkeypatch, + tmp_path, + _direct_url(url="https://github.com/ServiceNow/webarena-verified.git"), + ) with pytest.raises(RuntimeError, match="Lexmount fork"): runtime_source.verify_webarena_runtime_source() @@ -136,9 +138,7 @@ def test_consolidation_checks_source_before_reading_evidence(monkeypatch, tmp_pa lambda: (_ for _ in ()).throw(RuntimeError("wrong source")), ) with pytest.raises(RuntimeError, match="wrong source"): - catalog_consolidation.consolidate( - tmp_path / "missing", tmp_path, [], tmp_path / "sites" - ) + catalog_consolidation.consolidate(tmp_path / "missing", tmp_path, [], tmp_path / "sites") def test_runner_checks_source_before_browser_or_input_access(monkeypatch, tmp_path): @@ -177,6 +177,15 @@ def test_release_checks_source_before_profile_access(monkeypatch): "verify_webarena_runtime_source", lambda: (_ for _ in ()).throw(RuntimeError("wrong source")), ) - monkeypatch.setattr("sys.argv", ["release-check"]) + monkeypatch.setattr( + "sys.argv", + [ + "release-check", + "--profile", + "external-profile.json", + "--comparison-release", + "external-comparison.json", + ], + ) with pytest.raises(RuntimeError, match="wrong source"): catalog_release.main() diff --git a/tests/wcxb_fixture.py b/tests/wcxb_fixture.py index 2143af789..68fedc9ef 100644 --- a/tests/wcxb_fixture.py +++ b/tests/wcxb_fixture.py @@ -262,6 +262,7 @@ def create_report_inputs(tmp_path, *, english_source=True): original = load_jsonl(cohort['evidence_root'] / 'wcxb-scores.jsonl') profile = json.loads(cohort['profile_path'].read_bytes()) profile.update(source_repository='https://example.invalid/synthetic', source_commit='a' * 40, + paired_comparison={'baseline':'moli','candidate':'moli-pr643'}, candidate_source_commit='b' * 40, versions={provider: 'synthetic' for provider in PROVIDERS}) root = tmp_path / 'report' diff --git a/uv.lock b/uv.lock index d3c9c87c7..2cc83e2d8 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.11" resolution-markers = [ "python_full_version >= '3.13'", @@ -354,7 +354,7 @@ requires-dist = [ { name = "jieba", marker = "extra == 'webmainbench-eval'", specifier = "==0.42.1" }, { name = "json-repair", marker = "extra == 'browser-use-eval'", specifier = ">=0.54,<1" }, { name = "langchain-community", marker = "extra == 'oss-eval'", specifier = "==0.3.30" }, - { name = "lexbench-webfetch", git = "https://github.com/lexmount/lexbench-webfetch.git?rev=5ac291a592fec849d86bbb791a80579cc7a6c2c6" }, + { name = "lexbench-webfetch", git = "https://github.com/lexmount/lexbench-webfetch.git?rev=6bd263ed98030a883e66ac75a47ac4e7fbae4ad1" }, { name = "markdown", marker = "extra == 'webmainbench-eval'", specifier = ">=3.7,<4" }, { name = "openai", marker = "extra == 'webmainbench-eval'", specifier = ">=2.8,<4" }, { name = "openpyxl", marker = "extra == 'oss-eval'", specifier = "==3.1.5" }, @@ -1932,8 +1932,8 @@ wheels = [ [[package]] name = "lexbench-webfetch" -version = "0.2.0" -source = { git = "https://github.com/lexmount/lexbench-webfetch.git?rev=5ac291a592fec849d86bbb791a80579cc7a6c2c6#5ac291a592fec849d86bbb791a80579cc7a6c2c6" } +version = "0.3.0" +source = { git = "https://github.com/lexmount/lexbench-webfetch.git?rev=6bd263ed98030a883e66ac75a47ac4e7fbae4ad1#6bd263ed98030a883e66ac75a47ac4e7fbae4ad1" } dependencies = [ { name = "beautifulsoup4" }, { name = "soupsieve" }, @@ -3849,7 +3849,7 @@ resolution-markers = [ "python_full_version < '3.12'", ] dependencies = [ - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } wheels = [ @@ -3924,7 +3924,7 @@ resolution-markers = [ "python_full_version == '3.12.*'", ] dependencies = [ - { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.5.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" } wheels = [