Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,6 @@ ZENROWS_API_KEY=
SCRAPFLY_API_KEY=
OXYLABS_USERNAME=
OXYLABS_PASSWORD=
# Rainbond OpenAPI token used only by the disposable WebArena slice manager.
# Create it in Rainbond: Personal Center -> Access Token.
RAINBOND_API_TOKEN=
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ naming-check: runtime-check
test-python: runtime-check
$(TEST_PY) -m unittest discover -s tests -v
$(TEST_PY) -m pytest tests/browser_use -q
$(TEST_PY) -m pytest tests/environments tests/test_manage_webarena_rainbond_pool.py -q
$(TEST_PY) -m pytest tests/trajectory_replay -q

test-ui:
Expand Down
63 changes: 63 additions & 0 deletions deploy/webarena-rainbond/initializer.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/bin/sh
set -eu

wait_ctrl() {
url="$1"
attempts=0
until wget -qO- "$url/status" >/dev/null 2>&1; do
attempts=$((attempts + 1))
if [ "$attempts" -ge "${ENV_CTRL_MAX_ATTEMPTS:-360}" ]; then
echo "env-ctrl timeout: $url" >&2
exit 1
fi
sleep "${ENV_CTRL_RETRY_SECONDS:-5}"
done
}

init_ctrl() {
url="$1"
attempts=0
while :; do
set +e
response="$(wget -qO- --post-data='' "$url/init" 2>&1)"
result=$?
set -e
if echo "$response" | grep -Eq '"success"[[:space:]]*:[[:space:]]*true'; then
echo "$response"
return
fi
# env-ctrl can restart its managed process before flushing the HTTP response.
# BusyBox wget reports that successful reset path as an empty/broken response.
if [ "$result" -ne 0 ] && echo "$response" | grep -Eq 'error getting response|Connection reset'; then
wait_ctrl "$url"
echo "env-ctrl init completed after control endpoint restart: $url"
return
fi
attempts=$((attempts + 1))
if [ "$attempts" -ge "${ENV_CTRL_MAX_ATTEMPTS:-360}" ]; then
echo "env-ctrl init timeout: $url: $response" >&2
exit 1
fi
echo "env-ctrl init retry: $url" >&2
sleep "${ENV_CTRL_RETRY_SECONDS:-5}"
done
}

wait_ctrl "$REDDIT_CTRL_URL"
wait_ctrl "$GITLAB_CTRL_URL"
init_ctrl "$REDDIT_CTRL_URL"
init_ctrl "$GITLAB_CTRL_URL"

mkdir -p /www
printf 'ready\n' >/www/index.html
if busybox --list | grep -qx httpd; then
exec busybox httpd -f -p 8080 -h /www
fi
if busybox --list | grep -qx nc; then
while :; do
printf 'HTTP/1.1 200 OK\r\nContent-Length: 6\r\nConnection: close\r\n\r\nready\n' |
busybox nc -l -p 8080
done
fi
echo "initializer image has neither the httpd nor nc BusyBox applet" >&2
exit 1
21 changes: 21 additions & 0 deletions deploy/webarena/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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.
27 changes: 27 additions & 0 deletions deploy/webarena/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# WebArena 环境

Docker 和 Rainbond 环境由 `harness/environments/webarena/` 维护。任务和评分器来自
固定版本的 `webarena-verified` 依赖。镜像身份只有一份:
`evaluation/browser-use-public/webarena-environment-images.json`。

`runtime.example.json` 提供本地 Docker 的主机和端口模板;`site-delivery-contract.json`
描述镜像内端口、初始化方式与只读 Map 数据挂载。Map 为可选环境,契约的存在不会触发下载或部署。
有状态站点从镜像内的初始数据库重建,不挂载跨槽位共享的可写数据库目录。
GitLab 在 Rainbond 中需要独立的 2 GiB `/dev/shm` 内存挂载。

复制运行配置到 Git 外并填写 `hostname`,将 `image_lock` 改为镜像锁绝对路径:

```sh
uv run --frozen python scripts/manage_webarena_environment.py verify --config <配置> --site reddit
uv run --frozen python scripts/manage_webarena_environment.py start --config <配置> --site reddit --receipt <回执>
uv run --frozen python scripts/manage_webarena_environment.py reset --config <配置> --site reddit --receipt <回执>
uv run --frozen python scripts/verify_webarena_env_contract.py
```

Rainbond 的独立槽位、租约、并发访问检查和资源回收见
[运行示例](../../examples/trajectory_replay/public_webarena/README.md)。
运行配置、地址、凭据和回执保存在 Git 外;代码和配置模板可公开提交。

Docker 管理代码和 Map 校验契约源自
[`lexmount/webarena-env`](https://github.com/lexmount/webarena-env/tree/daecdf8b46e71ccc70d7cc2fdaf92a83af899624),
保留其 [MIT 许可证](LICENSE)。
49 changes: 49 additions & 0 deletions deploy/webarena/runtime.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"schema": "lexmount_webarena_runtime/1",
"image_lock": "../../evaluation/browser-use-public/webarena-environment-images.json",
"hostname": "${HOST}",
"sites": {
"shopping_admin": {
"port": 7780,
"control_port": 7781
},
"shopping": {
"port": 7770,
"control_port": 7771
},
"gitlab": {
"port": 8023,
"control_port": 8024
},
"reddit": {
"port": 9999,
"control_port": 9998
},
"map": {
"port": 3000,
"control_port": 3001
},
"wikipedia": {
"port": 8888,
"control_port": 8889
}
},
"credentials_env": {
"shopping_admin": {
"username": "WEBARENA_SHOPPING_ADMIN_USERNAME",
"password": "WEBARENA_SHOPPING_ADMIN_PASSWORD"
},
"shopping": {
"username": "WEBARENA_SHOPPING_USERNAME",
"password": "WEBARENA_SHOPPING_PASSWORD"
},
"reddit": {
"username": "WEBARENA_REDDIT_USERNAME",
"password": "WEBARENA_REDDIT_PASSWORD"
},
"gitlab": {
"username": "WEBARENA_GITLAB_USERNAME",
"password": "WEBARENA_GITLAB_PASSWORD"
}
}
}
137 changes: 137 additions & 0 deletions deploy/webarena/site-delivery-contract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"schema": "lexmount_webarena_site_delivery/1",
"image_lock": "../../evaluation/browser-use-public/webarena-environment-images.json",
"webarena_verified_version": "1.2.3",
"components": {
"shopping_admin": {
"architecture": "amd64",
"service_port": 80,
"control_port": 8877,
"health_path": "/",
"baseline": "fresh_container_writable_layer",
"init": "official_entrypoint",
"required_env_names": [
"WA_ENV_CTRL_EXTERNAL_SITE_URL",
"WA_ENV_CTRL_CMD_TIMEOUT_SEC"
],
"required_env_values": {
"WA_ENV_CTRL_CMD_TIMEOUT_SEC": "600"
},
"mounts": []
},
"shopping": {
"architecture": "amd64",
"service_port": 80,
"control_port": 8877,
"health_path": "/customer/account/login",
"baseline": "fresh_container_writable_layer",
"init": "official_entrypoint",
"required_env_names": [
"WA_ENV_CTRL_EXTERNAL_SITE_URL",
"WA_ENV_CTRL_CMD_TIMEOUT_SEC"
],
"required_env_values": {
"WA_ENV_CTRL_CMD_TIMEOUT_SEC": "600"
},
"mounts": []
},
"reddit": {
"architecture": "amd64",
"service_port": 80,
"control_port": 8877,
"health_path": "/login",
"baseline": "fresh_container_writable_layer",
"init": "official_explicit_env_ctrl",
"required_env_names": [
"WA_ENV_CTRL_EXTERNAL_SITE_URL"
],
"required_env_values": {},
"mounts": []
},
"gitlab": {
"architecture": "amd64",
"service_port": 8023,
"control_port": 8877,
"health_path": "/users/sign_in",
"baseline": "fresh_container_writable_layer",
"init": "official_explicit_env_ctrl",
"required_env_names": [
"WA_ENV_CTRL_EXTERNAL_SITE_URL",
"WA_ENV_CTRL_SKIP_RECONFIGURE"
],
"required_env_values": {
"WA_ENV_CTRL_SKIP_RECONFIGURE": "true"
},
"mounts": []
},
"map": {
"architecture": "amd64",
"service_port": 8080,
"control_port": 8877,
"health_path": "/",
"baseline": "verified_read_only_dataset",
"init": "official_dataset_setup",
"required_env_names": [
"WA_ENV_CTRL_EXTERNAL_SITE_URL"
],
"required_env_values": {},
"mounts": [
{
"volume": "webarena_verified_map_tile_db",
"path": "/data/database"
},
{
"volume": "webarena_verified_map_routing_car",
"path": "/data/routing/car"
},
{
"volume": "webarena_verified_map_routing_bike",
"path": "/data/routing/bike"
},
{
"volume": "webarena_verified_map_routing_foot",
"path": "/data/routing/foot"
},
{
"volume": "webarena_verified_map_nominatim_db",
"path": "/data/nominatim/postgres"
},
{
"volume": "webarena_verified_map_nominatim_flatnode",
"path": "/data/nominatim/flatnode"
},
{
"volume": "webarena_verified_map_website_db",
"path": "/var/lib/postgresql/14/main"
},
{
"volume": "webarena_verified_map_tiles",
"path": "/data/tiles"
},
{
"volume": "webarena_verified_map_style",
"path": "/data/style"
}
],
"data_urls": [
"https://webarena-map-server-data.s3.amazonaws.com/osm_tile_server.tar",
"https://webarena-map-server-data.s3.amazonaws.com/nominatim_volumes.tar",
"https://webarena-map-server-data.s3.amazonaws.com/osrm_routing.tar"
],
"data_archive_sha256": {
"osm_tile_server.tar": "2d802a915af14a13bcf393e9abd23d0de003eb043137736a54876410e05c4df2",
"nominatim_volumes.tar": "888ad53f754948f660796b6fcb440294a3dec647e141d606b1c7cb2616695baa",
"osrm_routing.tar": "99a4d10aae831fb3410b7d4743be03d9e02691283ec5105952839b84b9204806"
},
"data_archive_bytes": {
"osm_tile_server.tar": 41280327680,
"nominatim_volumes.tar": 124774901760,
"osrm_routing.tar": 21278935040
},
"data_archive_digest_basis": "5090_downloaded_bytes_not_upstream_signature"
}
},
"excluded_on_amd64": [
"wikipedia"
]
}
42 changes: 24 additions & 18 deletions docs/webarena-fixed-trajectory-reproduction.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# WebArena 固定轨迹回放:双仓库复现契约
# WebArena 固定轨迹回放:环境与复现

这是一套 RPA 式固定轨迹回放,不是 Agent browser-use。正式运行不调用 Agent、模型或
LLM judge;两个浏览器重放相同的 WebArena 公开人工轨迹,再由固定 evaluator 验收最终
回答、服务状态和 HAR。

## 两个仓库各自负责什么
## 代码与依赖

- `lexmount/webarena-env#2`:固定 WebArena-Verified 1.2.3、站点镜像 digest、原生架构、
端口、启动/重置和机器可读回执。
- 本仓库 PR:固定公开轨迹清单、离线修复记录、资格门禁、Chromium/Moli 镜像、动作
- `harness/environments/webarena/`:Docker 和 Rainbond 站点生命周期、初始化与机器可读回执;
`deploy/webarena/` 保存部署配置和数据挂载契约。
- `harness/trajectory_replay/public_webarena/`:公开轨迹清单、离线修复记录、资格门禁、Chromium/Moli 镜像、动作
重放、HAR、evaluator、一次失败重试、首次/最终成功统计、证据审计和独立报告发布。

两者共同足以在新机器上重新执行同一协议,但不会仅凭 Git 还原旧运行的逐字节证据:
WebArena-Verified 1.2.3 作为独立依赖提供任务和评分器;全部官方镜像只在
`evaluation/browser-use-public/webarena-environment-images.json` 中锁定。
本仓库提供在新机器上重新执行协议所需的代码,但不会仅凭 Git 还原旧运行的逐字节证据:
原始截图、HAR、attempts、manifest 和 summary 按仓库规则保存在 Git 外。Git 中的
`evaluation/browser-use-public/current-report.json` 保存当前公开结果与外部证据哈希,
用于证明报告绑定,而不是替代一次新运行。
Expand All @@ -28,14 +30,16 @@ LLM judge;两个浏览器重放相同的 WebArena 公开人工轨迹,再由

## 从空机器复现

1. 检出并固定两个 PR 的合并提交;两个仓库都执行 `uv sync --frozen` 和各自的检查。
再在本仓库运行
`uv run --frozen python scripts/verify_webarena_env_contract.py --environment-repo <webarena-env>`,
确认上游版本、六个站点、镜像 digest 和架构完全一致。
2. 在 `webarena-env` 复制 `config/runtime.example.json`,填写浏览器容器可访问的主机名,
运行 `webarena-env verify/start` 并保留回执。Map 的三份 tar 须先用
`uv run --frozen python -m webarena_env.map_archives --data-dir <下载目录>` 核对 SHA-256,
再按官方 setup 初始化九个数据卷。账号只通过环境变量提供。
1. 检出并固定本仓库提交,执行 `make setup` 和 `make ci`。运行
`uv run --frozen python scripts/verify_webarena_env_contract.py`,检查部署和评测
配置使用同一份镜像锁以及一致的端口、凭据变量名。
2. 复制 `deploy/webarena/runtime.example.json` 到 Git 外,填写可访问的 `hostname`,
将 `image_lock` 设置为本仓库镜像锁的绝对路径。使用
`uv run --frozen python scripts/manage_webarena_environment.py verify --config <配置> --site reddit`
校验环境,将 `verify` 换为 `start` 或 `reset` 启动或重建站点,通过 `--receipt <路径>` 保存回执。
仅在需要 Map 的任务范围内准备地图数据;`setup --config <配置> --site map --data-dir <目录>`
会先核对三份归档的 SHA-256,再运行官方初始化。当前 Rainbond 部署不包含 Map。
账号只通过环境变量提供。
3. 在本仓库复制 `examples/trajectory_replay/public_webarena/webarena.config.example.json`,使用同一组端口、
镜像 digest、网站地址与凭据变量名;运行环境 smoke,确认 CDP、登录、HAR 与站点 origin。
示例默认共用本机 Docker daemon,不假定存在某个用户的 rootless socket。若使用两条
Expand All @@ -55,9 +59,11 @@ uv run --frozen python -m harness.trajectory_replay.public_webarena.cli \
5. 运行 `examples/trajectory_replay/public_webarena/verify_metrics.py` 和报告打包器。任何缺失回答、动作、终止
原因、评分,或进入浏览器阶段后缺失截图、最终页面、HAR,都按失败处理。

正式运行前必须核对两个仓库的 WebArena-Verified 版本和六个站点镜像 digest 完全一致。
正式运行前必须核对 WebArena-Verified 版本和任务涉及的站点镜像 digest。
这项检查不允许用 mutable tag、跨架构模拟或手工修改数据库替代。

当前正式执行器按 Docker 容器身份验证并重建有状态站点,不能直接用 Rainbond 中的
远程有状态组件替代。Rainbond 的镜像部署和站点连通性可以先单独验证;要发布与本协议
等价的成绩,仍需受控重建与初态回执接入评测执行器。
执行器支持 Docker 容器重建和带回执的 Rainbond 独立槽位重建。每个并发执行者必须
独占一整个有状态槽位;只读 Wikipedia 可共享。Rainbond 使用方法见
[环境示例](../examples/trajectory_replay/public_webarena/README.md)。HTTP 并发测试只证明
网页可访问;正式运行仍须生成与本次重置、镜像、初态和就绪检查绑定的回执。
共享 Kiwix 的 amd64 部署与官方 arm64 镜像身份不同,不能直接沿用官方镜像的验证回执。
Loading
Loading