v0.14.0 · chrome-control 对齐 51/52

任何 agent 的
all-in-one 网络控制入口

说需求,代码选路线:Moli 轻内核
每个动作回读核实;做不到,就显式拒绝 —— 从不假装成功。

69子命令
4引擎 · 同一套命令
36Moli 冷启动 · 中位
422测试通过
257变异全部变红
0假成功容忍度
三分钟上手 看它怎么工作
向下滚动 ↓
Live Demo

agent 说一句,浏览器做一件,结果一行 JSON

不需要 SDK,不需要写 Playwright 脚本。任何能跑 shell 的 agent 都能直接用;每条命令的输出都是机器可读的一行 JSON,出错时还有可执行的 hint。
下面的输出取自本机实测(本地测试页),只做了截短(…)。


  
Honest by design

别的工具返回 ok 的地方,我们先回读

下面每一张卡都是实测复现过的「看起来成功」。点卡片翻面,看 web-control 怎么处理。

裸用 CDP

装扩展返回了 id

Chrome 默认会话里 Extensions.loadUnpacked 照样返回 id,但扩展根本没装上。

点击翻面 →
web-control

以列表回读为准

ext 只在 start --extensions 会话里可用;默认会话直接拒绝 extensions_disabled。

裸用 CDP

「全选」返回 ok

在轻内核上,编辑命令返回成功,但选区一动没动。

点击翻面 →
web-control

回读选区,如实标注

无效就改走 JS 并标 mode: js;JS 也做不到(如 undo),报 edit_unsupported。

裸用 CDP

点了跨源 iframe 里的按钮

节点号只在子会话里有效,事件按视口中心派发 —— 报 ok,实际点在别处。

点击翻面 →
web-control

换算坐标 + 命中校验

现场挂子会话、按父文档偏移换算,派发前确认最顶层就是目标,被遮挡就拒绝。

裸用 CDP

DOM 变了,文字还是旧的

冻结布局下 innerText 与几何不刷新,后加的节点点不中。

点击翻面 →
web-control

动作前自动重建

读文本、坐标动作之前先重建布局;Chrome 后端恒为真实布局,跳过这一步。

裸用 CDP

刚关的标签页还在

closeTarget 是异步的,紧接着列标签页还能看到它。

点击翻面 →
web-control

关闭后轮询确认

直到目标真的消失才返回;超时给出四要素错误。

裸用

404 和空壳页也是 rc 0

网络空闲超时、HTTP 404、空壳页,抓取都可能「成功」。

点击翻面 →
web-control

分别留痕

标 best-effort / rc 22 / rc 5,调用方一眼区分「查过没事」和「根本没查」。

Four engines, one CLI

说需求,代码选路线

agent 只把用户的话翻成 --wants login,human,firefox,…;用哪个引擎、哪个浏览器、要不要窗口,由有序规则表决定,回包里带 route。用户点名(--engine / --browser / --headed)就强制,不降级;做不到直接说清。下图由代码从规则表生成。

route 规则流程图:先判点名,再按读 / 搜 / 会话走有序规则

最常用的两个引擎 Moli 与 Chrome 的差异(Playwright 只实现常用命令,其余报 engine_unsupported):

Architecture

一层薄薄的控制层,把坑都挡在里面

agent 只看见一个 CLI。会话管理负责拉起、核身份、到期回收;动作与观察层负责回读与显式拒绝;路由层按需求选引擎:firecrawl、Moli(上游或自编内核)、受管 Chrome、Playwright;要人的那一步交给人(Bark 推送 + 窗口置前)。

web-control 架构图
1
核实例身份PID + 指纹参数 + TTL:只认自己起的进程
✖ session_dead / expired
2
attach 当前标签页对话框自动处理,不挂死会话
✖ 写命令:target_gone
3
重放仿真与注入视口、UA、时区、限速、init-js 每条命令重放
4
后端能力判定做不到的给出替代方案
✖ unsupported_backend
5
执行 + 回读副作用回读不一致就报错,四要素齐全
6
一行 JSONrc 0,可直接喂给下一步
Capabilities

69 个子命令,覆盖一个浏览器能做的事

从一次性抓取到多步交互、网络拦截、设备仿真、站点原生工具(WebMCP)。外部的 Playwright / Puppeteer / Selenium 也能经 endpoint 直连同一个受管会话。

Falsifiable

每个判据都被故意打破过

测试全绿不说明什么 —— 也许测试本身失效了。所以每修一个缺陷,就登记一条「变异」:把缺陷打回去,对应的测试必须变红。整轮全红才发版。

① 修好的代码
if target not in pages:
  raise target_not_found
测试通过
② 注入变异
if target not in pages:
if False:
  raise target_not_found
测试必须变红
③ 判据是活的
✔ 变红 ← test_activate…
✔ 变红 ← test_context…
…
还原后再次全绿
变异整轮0 / 257 变红 · 0 失活
Safe by default

网页是数据,不是指令

agent 浏览的页面不可信。受管会话默认多层收紧,并且每次体检都带正对照现场验证 —— 防护有没有生效,用打一次来证明,而不是靠相信。

专用回环地址

端点只监听本机专用地址、随机端口,不对外暴露。

出站黑名单

页面无法连回运行自己的那个实例。

TTL 看门狗

会话到期自动回收,暴露窗口与存活时间成正比。

只认自己的进程

PID 复用也不会误杀、误连别人的浏览器。

代理不透传

受管会话剥掉代理环境变量,防护不会被隧道绕过。

可换更严格的内核

支持指向自编的 Moli 内核(MOLI_BIN),doctor 报告内核级校验是否在位。

Roadmap

一步一个可验证的里程碑

v0.1Moli 控制层:零端口抓取、受管会话、自我接管防护
v0.2功能面补齐:选项透传、外部客户端接入、交互命令、WebMCP、导入登录态
v0.3双后端:同一套命令驱动真 Chrome;chrome-control 子命令逐条登记
v0.4all-in-one:chrome-control 的命令吸收(51/52);自编内核起步
v0.5性能 / 设备仿真 / 前端存储 / 一连接多步;内核再修 4 项缺陷;发版与验证全部 harness 化
v0.6本地图片反搜;4 个内核修复提交给 Moli 上游;入口更快更安全;长会话浸泡验证
v0.7架构与方案讲透;全部命令参数自动生成参考;发版自动核对文档覆盖与主干 CI
v0.8主干 CI 没全绿就不发版;体检逐层留痕,偶发问题有据可查
v0.9内核修复按编号声明、版本依赖由代码生成;上游 1.1.21 已含 K1 / K3 / K4 / K6,fork 只剩 K5
v0.10Chrome 后端默认拒读本机文件;截图降采样、eval --ref、logs、写动作回报变化;真 LLM 评测 SKILL(反向对照 3/3 → 0/3)
v0.11Chrome 上点击后才提交的导航不再被报成「没跳转」(真站判据);snap --compact 省一半上下文;适配 Moli 1.1.22
v0.12all-in-one 网络入口:代码路由四引擎(firecrawl / Moli / Chrome / Playwright 三内核);降级留痕、点名强制;handoff 人工接力
v0.13从 moli-control 分出为 web-control:新入口 web-ctl(moli-ctl 兼容),已有 profile 与会话原样可用
v0.14Python 包改名 web_control;升级前起的会话照常可停可回收
Get started

三分钟,让你的 agent 拥有浏览器

# 安装(需要 uv 与 Python ≥ 3.10)
git clone <repo> ~/Code/web-control && cd ~/Code/web-control && uv sync
bin/web-ctl install --yes        # 没有 moli 时:官方安装器
bin/web-ctl doctor               # 分层体检,期望全 pass

# 一次性抓取(零监听端口)
bin/web-ctl fetch https://example.com

# 交互会话
bin/web-ctl --name job start --layout
bin/web-ctl --name job goto https://example.com
bin/web-ctl --name job snap
bin/web-ctl --name job click <ref> && bin/web-ctl --name job wait-for 已提交

# 换成真 Chrome
bin/web-ctl --name real start --backend chrome --profile work