事情开始于一个简单的测试:「测试一下 cloakbrowser 能不能用。」
一个诡异的退化
CloakBrowser 是一个防检测浏览器——它在 Chromium 源码层面打了 48 个 C++ 补丁,能通过 reCAPTCHA v3、Cloudflare Turnstile、FingerprintJS 等几乎所有 bot 检测。npm 安装后,API 长这样:
import { launch } from 'cloakbrowser';
const browser = await launch();这是它 README 的第一行代码。
但 OpenCode 里的 agent 写了这个:
import { chromium } from 'playwright';
const browser = await chromium.launch({
executablePath: '~/.cloakbrowser/chromium-.../chrome'
});它把 cloakbrowser 当成了 Playwright 的 drop-in 二进制,而不是一个有自己 API 的包。 完全跳过了 cloakbrowser 自己的 launch() 函数,直接把它当原始 Chromium 喂给 Playwright。
第一步:我以为问题在 agent 身上
我的第一反应很自然:「agent 没读文档。」
cloakbrowser 有 package.json,有 README,有 exports 字段。只要读了其中任何一个,都不会犯这个错。问题是 agent 没读——它看到 cloakbrowser info 输出一个二进制路径,就直接模式匹配到了 chromium.launch({executablePath})。
这看起来是一个可以通过规则约束解决的问题。我花了相当的时间做了一个 docs-before-code skill,按照完整的 RED-GREEN-REFACTOR 方法论:
| 阶段 | 测试 | 结果 |
|---|---|---|
| RED 基线 | agent 用 listr2 写代码(不读文档) | 暴露了「我知道这个库」的合理化 |
| GREEN 验证 | agent 用 vitest 程序化 API(按 skill 规则) | 先查 vitest/node 子路径,再写代码 ✅ |
| REFACTOR 压力 | agent 用 execa v9(2 分钟限制 + 旧记忆) | 发现 tagged template 新语法,避免写旧 API ✅ |
这个 skill 本身是有效的。它的 Gate Function(五个步骤:IDENTIFY → LOCATE → VERIFY → CONFIRM → 写代码)能在 agent 写 import 语句之前拦下它,强制它先去读 package.json 的 exports 字段和 README 的第一段代码。execa v9 的压力测试证明了这一点——即使 agent 的记忆里是 execa('cmd', [args]) 的旧 API,skill 也强迫它先查了文档,发现了 v9 的 tagged template 新语法。
但我很快就发现,cloakbrowser 的问题完全没有被解决。
第二步:发现 skill 完全没用
我回去翻其他会话记录。三个不同的 session 里,用户明确说了「用 cloakbrowser」,agent 全都退化到了 Playwright MCP 工具。
其中一个 session 的转写特别刺眼:
User: "设置界面和你说的不一样,你用 cloakbrowser 配置"
Agent: → 加载 dev-browser skill
Agent: → 加载 playwright skill
Agent: → 调用 playwright MCP 的 browser_navigate
Agent: → 完全没用 cloakbrowseragent 根本没走到写 import 那一步。 它没有写 Node.js 脚本、没有 npm install、没有 import 语句。它直接调了 Playwright MCP 的 browser_navigate、browser_click 这些现成的工具。
我花了两天写的 docs-before-code skill,在真实场景里一次都没触发过。不是它写错了,是它的触发条件(写 import 语句)和故障场景(MCP 工具选择)根本不在同一层。
这就好比你给司机装了一个「开车前检查后视镜」的提醒,但他连车都没上——他在车库门口就被导航系统指引到另一辆车上了。
第三步:追到 OMO 源码
如果 agent 是在「工具选择」阶段被拐走的,那一定有什么东西在系统 prompt 里告诉它「浏览器任务必须用 Playwright」。
我开始翻系统配置。OpenCode 的 opencode.jsonc 里没有 Playwright MCP 配置。AGENTS.md 里也没有写死 Playwright。那这个偏好是哪里来的?
答案在 OMO(Oh-My-OpenCode)插件里。在其缓存安装目录 oh-my-openagent/dist/ 的源码中,我找到了这段:
var playwrightSkill = {
name: "playwright",
description: "MUST USE for any browser-related tasks. Browser automation via Playwright MCP ...",
};“MUST USE for any browser-related tasks” —— 这是一条注入到 agent 系统 prompt 里的死命令。当用户说「用 cloakbrowser」时,agent 的语义解析是这样的:
"cloakbrowser" → 包含 "browser" → 这是 browser-related task → "MUST USE playwright"系统级指令(skill description)的优先级比用户消息更高。用户的 cloakbrowser 被语义压缩成了 browser,然后被 MUST USE 抢占了。
进一步分析确认了 OMO 有一个 browser_automation_engine 配置项:
export const BrowserAutomationProviderSchema = z.enum([
"playwright",
"agent-browser",
"dev-browser",
"playwright-cli",
// 没有 "cloakbrowser"
])
provider: BrowserAutomationProviderSchema.default("playwright")可选值只有四个,默认是 playwright。cloakbrowser 不在选项里。
这意味着:即使用户完全不配置任何东西,agent 看到的系统 prompt 里也会有一行 playwright: MUST USE for any browser-related tasks。这不是 OMO 故意排斥 cloakbrowser——它只是还没被适配进去。
但这件事揭示了一个更深的问题:当框架有一个「强制指定工具」的机制时,不在名单里的工具根本就没有被正确使用的可能性。 无论你写多少 skill、优化多少 prompt,agent 都会被上游的「MUST USE」指令拖走。
修框架本身
理解了根因之后,修复就很清晰了:把 cloakbrowser 加进 browser_automation_engine 的 provider 选项。
我 fork 了 OMO 仓库,在 feat/cloakbrowser-provider 分支上改了 4 个文件,总共 +126 行:
| 文件 | 改动 |
|---|---|
src/config/schema/browser-automation.ts | 添加 "cloakbrowser" 到 provider enum |
src/features/builtin-skills/skills/cloakbrowser.ts | 新建,119 行的 skill 模板,覆盖完整 API |
src/features/builtin-skills/skills/index.ts | 导出 cloakbrowserSkill |
src/features/builtin-skills/skills.ts | 在 createBuiltinSkills 里添加 cloakbrowser 分支 |
新增的 cloakbrowser skill 包含了完整的 API 参考:三种启动方式(launch、launchContext、launchPersistentContext)、配置选项(headless、proxy、timezone、geoip、humanize)、CLI 工具、reCAPTCHA 优化技巧、与原生 Playwright 的关键差异。
合并后,用户只需在 oh-my-openagent.json 里加一行:
{ "browser_automation_engine": { "provider": "cloakbrowser" } }agent 就不会再看到 playwright: MUST USE for any browser-related tasks,而是看到 cloakbrowser 的 skill,知道什么时候用它、怎么用它。
PR 已提交
PR #4337:feat: add cloakbrowser as browser automation engine provider
- TypeScript 类型检查:✅ 零错误
browser_automation_engineschema 测试:✅ 3/3 通过- 构建:✅ 成功
反思
这件事最反直觉的地方在于:你以为是 agent 没读文档,最后发现是文档根本不在 agent 的视野里。
我花了两天写了一个精心测试过的 skill,它确实能解决它设计要解决的问题(agent 写 import 之前先查 API),但它对 cloakbrowser 的退化问题毫无作用——因为那个问题根本不在代码编写层。
docs-before-code 在工厂流水线的「组装」环节装了一个质检站,但我真正需要修的是「零件进料口」——工具选择那一层。错误不在 agent 怎么写代码,而在 agent 一开始就被给了错误的工具。
这也是一条给 AI agent 框架开发者的原则:如果你的框架有一个「强制指定工具」的机制,确保新工具能平等地接入它。 否则,你的框架就不是助推器,而是壁垒。