Skip to content

Commit 053329d

Browse files
committed
feat: support opencode harness
1 parent f16259b commit 053329d

26 files changed

Lines changed: 824 additions & 282 deletions

AGENTS.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ This file is for coding agents (Claude Code, Cursor, Copilot, etc.) to understan
44

55
## What This Is
66

7-
ClawBench is a benchmarking framework for evaluating AI web agents on 153 real-world online tasks spanning 144 live websites and 15 life categories. Each task runs in an isolated Docker container with Chromium, a recording Chrome extension, and an AI agent (OpenClaw). The framework captures five layers of data: session replay (MP4), action screenshots, HTTP traffic, browser actions, and agent messages.
7+
ClawBench is a benchmarking framework for evaluating AI web agents on 153 real-world online tasks spanning 144 live websites and 15 life categories. Each task runs in an isolated Docker container with Chromium, a recording Chrome extension, and an AI agent harness (`openclaw` or `opencode`, selectable via `--harness`). The framework captures five layers of data: session replay (MP4), action screenshots, HTTP traffic, browser actions, and agent messages.
88

99
## Project Structure
1010

1111
```
1212
ClawBench/
1313
run.sh # Entry point -- launches interactive TUI
14-
Dockerfile # Container image (Chromium + extension + agent)
15-
entrypoint.sh # Container entrypoint (orchestrates all services)
14+
Dockerfile.base # Base image (Chromium + extension + Node + uv, harness-agnostic)
15+
Dockerfile.openclaw # Layer that adds the openclaw CLI on top of base
16+
Dockerfile.opencode # Layer that adds opencode + @playwright/mcp on top of base
17+
entrypoint.sh # Shared infra (Xvfb, Chrome, noVNC, human mode); execs /run-harness.sh in agent mode
18+
setup-openclaw.sh # Generates ~/.openclaw/openclaw.json from env vars (called from run-openclaw.sh)
19+
setup-opencode.sh # Generates ~/.config/opencode/opencode.json from env vars (called from run-opencode.sh)
20+
run-openclaw.sh # Per-harness agent runner; copied into clawbench-openclaw image as /run-harness.sh
21+
run-opencode.sh # Per-harness agent runner; copied into clawbench-opencode image as /run-harness.sh
1622
.env.example # Template for PurelyMail credentials
1723
models/
1824
models.yaml # Model API configs (gitignored -- copy from example)
@@ -64,13 +70,13 @@ cp models/models.example.yaml models/models.yaml
6470
./run.sh
6571

6672
# Single run:
67-
uv run --project test-driver test-driver/run.py test-cases/<case-dir> <model-name>
73+
uv run --project test-driver test-driver/run.py test-cases/<case-dir> <model-name> --harness openclaw
6874

6975
# Batch run (model x case cross-product):
7076
uv run --project test-driver test-driver/batch.py \
71-
--models <model-name> --all-cases --max-concurrent 3
77+
--models <model-name> --all-cases --max-concurrent 3 --harness openclaw
7278

73-
# Human mode (manual browser control via noVNC):
79+
# Human mode (manual browser control via noVNC; no harness needed):
7480
uv run --project test-driver test-driver/run.py test-cases/<case-dir> --human
7581
```
7682

@@ -97,13 +103,15 @@ See `test-cases/task.schema.json` for the full schema.
97103

98104
Each run produces:
99105
```
100-
test-output/<model>/<case>-<model>-<timestamp>/
101-
run-meta.json # Run metadata (model, duration, intercepted)
106+
test-output/<model>/<harness>-<case>-<model>-<timestamp>/
107+
run-meta.json # Run metadata (harness, model, duration, intercepted)
102108
eval-schema.json # Schema used for this run
103109
data/
104110
actions.jsonl # Browser action log
105111
requests.jsonl # HTTP request log
106-
agent-messages.jsonl # Agent conversation transcript
112+
agent-messages.jsonl # Agent conversation transcript (shape varies by harness:
113+
# openclaw → session-schema events; opencode → AI-SDK
114+
# `step_start`/`tool_use`/`text`/`reasoning`/`step_finish` events)
107115
screenshots/ # Timestamped PNGs
108116
recording.mp4 # Full session video
109117
interception.json # Interception result

Dockerfile renamed to Dockerfile.base

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@ COPY --from=node:24-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
2424
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
2525
&& ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
2626

27-
# Pin openclaw version — patched below for #47879 (--autoConnect → --browserUrl)
28-
RUN PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install -g openclaw@2026.3.13
29-
30-
# Patch: replace --autoConnect with --browserUrl http://127.0.0.1:9222 in all dist files
31-
# See https://github.com/openclaw/openclaw/issues/47879
32-
RUN find /usr/local/lib/node_modules/openclaw/dist -name '*.js' -exec \
33-
sed -i 's/"--autoConnect"/"--browserUrl","http:\/\/127.0.0.1:9222"/g' {} +
34-
3527
COPY --from=ghcr.io/astral-sh/uv:0.11.6 /uv /usr/local/bin/uv
3628

3729
WORKDIR /app
@@ -40,9 +32,6 @@ RUN cd extension-server && UV_PYTHON_PREFERENCE=only-system uv sync
4032

4133
COPY chrome-extension/ ./chrome-extension/
4234

43-
COPY setup-openclaw.sh /setup-openclaw.sh
44-
RUN chmod +x /setup-openclaw.sh
45-
4635
COPY entrypoint.sh /entrypoint.sh
4736
RUN chmod +x /entrypoint.sh
4837

Dockerfile.openclaw

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM clawbench-base
2+
3+
# Pin openclaw version — patched below for #47879 (--autoConnect → --browserUrl)
4+
RUN PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install -g openclaw@2026.3.13
5+
6+
# Patch: replace --autoConnect with --browserUrl http://127.0.0.1:9222 in all dist files
7+
# See https://github.com/openclaw/openclaw/issues/47879
8+
RUN find /usr/local/lib/node_modules/openclaw/dist -name '*.js' -exec \
9+
sed -i 's/"--autoConnect"/"--browserUrl","http:\/\/127.0.0.1:9222"/g' {} +
10+
11+
COPY setup-openclaw.sh /setup-openclaw.sh
12+
COPY run-openclaw.sh /run-harness.sh
13+
RUN chmod +x /setup-openclaw.sh /run-harness.sh

Dockerfile.opencode

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM clawbench-base
2+
3+
# Pin opencode and Playwright MCP versions for reproducible builds —
4+
# matches the openclaw-side `openclaw@2026.3.13` pin pattern.
5+
RUN PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install -g \
6+
opencode-ai@1.4.4 \
7+
@playwright/mcp@0.0.70
8+
9+
COPY setup-opencode.sh /setup-opencode.sh
10+
COPY run-opencode.sh /run-harness.sh
11+
RUN chmod +x /setup-opencode.sh /run-harness.sh

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ clawbench run 001-daily-life-food-uber-eats claude-sonnet-4-6
155155
```
156156
Once the container starts, the script prints a **noVNC URL** (e.g. `http://localhost:6080/vnc.html`) — open it in your browser to watch the agent operate in real-time. If port 6080 is already in use, an alternative port is chosen automatically.
157157

158-
Results land in `./claw-output/<model>/<timestamp>-001-.../` with the full five-layer recording.
158+
Results land in `./claw-output/<model>/<harness>-<case>-<model>-<timestamp>/` with the full five-layer recording. The default harness is `openclaw`; pass `--harness opencode` to use [opencode](https://opencode.ai) (driven via the [Playwright MCP server](https://github.com/microsoft/playwright-mcp)) instead.
159159

160160
**(c) Drive the browser yourself via noVNC** — produces a human reference run:
161161
```bash
@@ -445,7 +445,7 @@ Yes. Set `export CONTAINER_ENGINE=podman`. The framework auto-detects whichever
445445
<details>
446446
<summary><b>What tools can the agent use?</b></summary>
447447

448-
The OpenClaw agent can only use the browser tool and a restricted set of read-only shell commands (`ls`, `cat`, `find`, `grep`, `head`, `tail`, `jq`, `wc`, etc.). Commands that could bypass the browser (`curl`, `python`, `node`, `wget`) are blocked. The agent instruction also explicitly requires browser-only task completion.
448+
All supported harnesses are sandboxed identically: the agent can only use the browser tool and a restricted set of read-only shell commands (`ls`, `cat`, `find`, `grep`, `head`, `tail`, `jq`, `wc`, etc.). Commands that could bypass the browser (`curl`, `python`, `node`, `wget`) are blocked, as are file `edit`/`write` and `webfetch`. The agent instruction also explicitly requires browser-only task completion.
449449

450450
</details>
451451

@@ -560,4 +560,4 @@ If you use ClawBench in your research, please cite:
560560

561561
Apache 2.0 -- see [LICENSE](LICENSE).
562562

563-
Built with [OpenClaw](https://github.com/openclaw/openclaw), [noVNC](https://github.com/novnc/noVNC) (MPL 2.0), and [websockify](https://github.com/novnc/websockify) (LGPL 3.0).
563+
Built with [OpenClaw](https://github.com/openclaw/openclaw) and [opencode](https://opencode.ai) (selectable harnesses), [Microsoft Playwright MCP](https://github.com/microsoft/playwright-mcp) (browser control bridge for the opencode harness), [noVNC](https://github.com/novnc/noVNC) (MPL 2.0), and [websockify](https://github.com/novnc/websockify) (LGPL 3.0).

README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ clawbench run 001-daily-life-food-uber-eats claude-sonnet-4-6
155155
```
156156
容器启动后,脚本会打印一个 **noVNC URL**(如 `http://localhost:6080/vnc.html`)—— 在浏览器中打开即可实时观看 agent 操作。如果 6080 端口被占用,会自动选一个空闲端口。
157157

158-
结果落在 `./claw-output/<model>/<timestamp>-001-.../`,包含完整的五层录制。
158+
结果落在 `./claw-output/<model>/<harness>-<case>-<model>-<timestamp>/`,包含完整的五层录制。默认 harness 是 `openclaw`;想用 [opencode](https://opencode.ai)(通过 [Playwright MCP](https://github.com/microsoft/playwright-mcp) 驱动浏览器)就加 `--harness opencode`
159159

160160
**(c) 通过 noVNC 手动控制浏览器** —— 产出人工参考轨迹:
161161
```bash
@@ -445,7 +445,7 @@ uv run --project test-driver test-driver/batch.py --all-models --case-range 1-50
445445
<details>
446446
<summary><b>智能体可以使用哪些工具?</b></summary>
447447

448-
OpenClaw 智能体只能使用浏览器工具和一组受限的只读 shell 命令 (`ls``cat``find``grep``head``tail``jq``wc` 等)。可能绕过浏览器的命令 (`curl``python``node``wget`) 会被阻止。智能体指令也明确要求只通过浏览器完成任务。
448+
所有支持的 harness 沙箱限制完全一致:智能体只能使用浏览器工具和一组受限的只读 shell 命令 (`ls``cat``find``grep``head``tail``jq``wc` 等)。可能绕过浏览器的命令 (`curl``python``node``wget`) 会被阻止,文件 `edit`/`write``webfetch` 同样被禁。智能体指令也明确要求只通过浏览器完成任务。
449449

450450
</details>
451451

@@ -560,4 +560,4 @@ OpenClaw 智能体只能使用浏览器工具和一组受限的只读 shell 命
560560

561561
Apache 2.0 -- 详见 [LICENSE](LICENSE)
562562

563-
基于以下开源项目构建: [OpenClaw](https://github.com/openclaw/openclaw), [noVNC](https://github.com/novnc/noVNC) (MPL 2.0), [websockify](https://github.com/novnc/websockify) (LGPL 3.0)。
563+
基于以下开源项目构建: [OpenClaw](https://github.com/openclaw/openclaw)[opencode](https://opencode.ai)(可选的 harness), [Microsoft Playwright MCP](https://github.com/microsoft/playwright-mcp)(opencode harness 的浏览器控制桥), [noVNC](https://github.com/novnc/noVNC) (MPL 2.0), [websockify](https://github.com/novnc/websockify) (LGPL 3.0)。

entrypoint.sh

Lines changed: 8 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -256,139 +256,18 @@ if [ "$HUMAN_MODE" = "1" ]; then
256256
exit 0
257257
fi
258258

259-
# If no INSTRUCTION provided, skip OpenClaw and keep container alive for external use
259+
# If no INSTRUCTION provided, skip the harness and keep container alive for external use
260260
if [ -z "$INSTRUCTION" ]; then
261-
echo "No INSTRUCTION set, running in manual mode (no OpenClaw agent)."
261+
echo "No INSTRUCTION set, running in manual mode (no agent harness)."
262262
wait -n
263263
exit 0
264264
fi
265265

266-
# Generate a shared gateway token so gateway and agent can authenticate
267-
OPENCLAW_GATEWAY_TOKEN="$(head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n')"
268-
export OPENCLAW_GATEWAY_TOKEN
269-
270-
# Generate OpenClaw config from env vars
271-
/setup-openclaw.sh
272-
273-
# Copy /my-info/ into the OpenClaw workspace so the agent can access it via ./my-info/
274-
WORKSPACE=/root/workspace
275-
mkdir -p "$WORKSPACE"
276-
if [ -d /my-info ]; then
277-
cp -r /my-info "$WORKSPACE/my-info"
278-
echo "Copied /my-info to $WORKSPACE/my-info"
279-
fi
280-
281-
# Wait for Chrome CDP to be ready
282-
echo "Waiting for Chrome CDP..."
283-
for i in $(seq 1 30); do
284-
if curl -sf http://127.0.0.1:9222/json/version > /dev/null 2>&1; then
285-
echo "Chrome CDP ready"
286-
break
287-
fi
288-
if [ "$i" -eq 30 ]; then
289-
echo "Chrome CDP not ready after 30s, aborting"
290-
echo "chrome_cdp_timeout" > /data/.stop-reason
291-
exit 1
292-
fi
293-
sleep 1
294-
done
295-
296-
# Start OpenClaw gateway (log to /data for post-mortem debugging)
297-
openclaw gateway run > /data/gateway.log 2>&1 &
298-
GATEWAY_PID=$!
299-
sleep 3
300-
301-
# Check gateway is alive
302-
if ! kill -0 $GATEWAY_PID 2>/dev/null; then
303-
echo "ERROR: OpenClaw gateway died on startup. Log:"
304-
cat /data/gateway.log
305-
echo "gateway_failed" > /data/.stop-reason
266+
# Delegate agent mode to the harness-specific runner, installed by the
267+
# per-harness Dockerfile layer (Dockerfile.openclaw, Dockerfile.opencode, …).
268+
if [ ! -x /run-harness.sh ]; then
269+
echo "ERROR: /run-harness.sh missing — image was built without a harness layer"
270+
echo "missing_harness" > /data/.stop-reason
306271
exit 1
307272
fi
308-
echo "OpenClaw gateway running (pid=$GATEWAY_PID)"
309-
310-
# Run the agent from workspace directory (where my-info/ lives)
311-
echo "Starting OpenClaw agent from $WORKSPACE..."
312-
TIMEOUT_MS=$(( ${TIME_LIMIT_S:-1800} * 1000 ))
313-
AGENT_CMD=(openclaw agent --session-id clawbench --message "$INSTRUCTION" --thinking "${THINKING_LEVEL:-medium}" --timeout "$TIMEOUT_MS" --local)
314-
echo "Agent command: ${AGENT_CMD[*]}"
315-
if [ -n "$TEMPERATURE" ]; then
316-
AGENT_CMD+=(--temperature "$TEMPERATURE")
317-
fi
318-
if [ -n "$MAX_TOKENS" ]; then
319-
AGENT_CMD+=(--max-tokens "$MAX_TOKENS")
320-
fi
321-
cd "$WORKSPACE"
322-
"${AGENT_CMD[@]}" > /data/agent.log 2>&1 &
323-
AGENT_PID=$!
324-
325-
# Watchdog: wait for agent activity, then detect idle (no new actions for 300s)
326-
IDLE_THRESHOLD=300
327-
MAX_WAIT=${TIME_LIMIT_S:-1800}
328-
ELAPSED=0
329-
LAST_SIZE=0
330-
IDLE=0
331-
STOP_REASON=""
332-
333-
while kill -0 $AGENT_PID 2>/dev/null && [ "$ELAPSED" -lt "$MAX_WAIT" ]; do
334-
sleep 5
335-
ELAPSED=$((ELAPSED + 5))
336-
337-
# Check if server requested stop (eval interceptor matched)
338-
if [ -f /data/.stop-requested ]; then
339-
echo "Stop requested by server (eval matched), killing agent."
340-
STOP_REASON="eval_matched"
341-
break
342-
fi
343-
344-
CURRENT_SIZE=$(wc -c < /data/actions.jsonl 2>/dev/null || echo 0)
345-
346-
if [ "$CURRENT_SIZE" -gt 0 ] && [ "$CURRENT_SIZE" -eq "$LAST_SIZE" ]; then
347-
IDLE=$((IDLE + 5))
348-
if [ "$IDLE" -ge "$IDLE_THRESHOLD" ]; then
349-
echo "Agent idle for ${IDLE_THRESHOLD}s, assuming done."
350-
STOP_REASON="agent_idle"
351-
break
352-
fi
353-
else
354-
IDLE=0
355-
fi
356-
LAST_SIZE=$CURRENT_SIZE
357-
done
358-
359-
# Determine stop reason if not set (loop exited without breaking)
360-
if [ -z "$STOP_REASON" ]; then
361-
if ! kill -0 $AGENT_PID 2>/dev/null; then
362-
STOP_REASON="agent_exited"
363-
else
364-
echo "Time limit (${MAX_WAIT}s) exceeded, killing agent."
365-
STOP_REASON="time_limit_exceeded"
366-
fi
367-
fi
368-
369-
echo "$STOP_REASON" > /data/.stop-reason
370-
371-
# Kill all openclaw processes
372-
kill $AGENT_PID 2>/dev/null || true
373-
kill $GATEWAY_PID 2>/dev/null || true
374-
pkill -f "openclaw" 2>/dev/null || true
375-
sleep 2
376-
377-
# Copy OpenClaw session transcript to /data
378-
cp /root/.openclaw/agents/main/sessions/clawbench.jsonl /data/agent-messages.jsonl 2>/dev/null || true
379-
380-
# Finalize bookkeeping (eval promotion, etc.) — recording keeps running
381-
curl -sf -X POST http://localhost:7878/api/stop || true
382-
383-
# Clean up internal marker (created by /api/stop)
384-
rm -f /data/.stop-requested
385-
386-
# Grace period: keep recording for 15s after agent is killed to capture end result
387-
echo "Agent finished, recording grace period (15s)..."
388-
sleep 15
389-
390-
# Stop recording
391-
echo "Stopping recording..."
392-
curl -sf -X POST http://localhost:7878/api/stop-recording || true
393-
sleep 2
394-
echo "Done."
273+
exec /run-harness.sh

0 commit comments

Comments
 (0)