BrowserSkill AI浏览器自动化方案 到底是怎么做的?
一、项目定位
BrowserSkill 是腾讯开源的一款本地桥接中间件,核心使命是:
把 AI Agent(Claude Code、Cursor、Codex、Kimi Code 等)连接到用户已经登录的真实 Chrome 浏览器,让 Agent 直接复用你的 Cookie 和 Session 来操作网页,无需任何额外登录。
它不是一个 AI 框架,也不内置任何 LLM 调用逻辑。它只做一件事:提供一个 bsk 命令行工具,让外部的任意 Agent 通过 Shell 调用来驱动浏览器。这个设计使它与任何 Agent Harness 完全解耦。
与同类工具的核心差异
| 维度 | BrowserSkill | Playwright MCP | browser-use |
|---|---|---|---|
| 浏览器实例 | 复用用户真实登录态 | 新建独立进程(空 Profile) | 默认新建进程 |
| 用户干扰 | Agent Window 完全隔离,不影响用户正常浏览 | 通常独占或共享实例 | 框架内部管理 |
| Agent 接入 | 任意 Shell 调用(bsk CLI);skill 文件教 LLM 用法 | 需 MCP 客户端 | Python 代码调用 |
| AI 模型耦合 | 完全解耦 | 解耦 | 深度集成 langchain |
| Human-in-loop | 内置 request-help,页面叠加层交互 | 无 | 无 |
| 语义观察层 | 自研 VOM(AX树+DOM融合,含 surfaceProbe) | aria snapshot | 截图+DOM混合 |
| 多浏览器 | 支持同时连接多实例,--browser <id> 指定 | 每个进程独立 | — |
| Skill 分发 | bsk install-skill 一键部署到 12 种 harness | 无 | 无 |
二、整体架构
2.1 系统拓扑

2.2 技术栈
| 层次 | 语言/框架 | 关键依赖 |
|---|---|---|
| CLI + Daemon | Rust 2024 edition(tokio 全特性) | clap 4 / serde-json / semver / reqwest / tokio-tungstenite |
| 协议层 | Rust(bsk-protocol crate) | schemars 0.8(生成 JSON Schema 供 TS 同步) |
| 浏览器扩展 | TypeScript + React 19 | WXT(MV3)/ Vite / Tailwind CSS v4 |
| VOM 语义层 | TypeScript 纯逻辑库 | @browser-skill/vom(无运行时依赖) |
| 构建 | Cargo Workspace + pnpm Workspace | Biome(lint/format)/ vitest |
2.3 IPC 协议帧格式
CLI 与 Daemon 之间、Daemon 与 Extension 之间均使用相同的 JSON 帧结构(每行一帧):
RequestFrame → { "id": "rpc-1", "method": "tool.click", "params": {...} }
ResponseFrame → { "id": "rpc-1", "body": { "ok": {...} } }
或 { "id": "rpc-1", "body": { "err": { "code": "...", "message": "..." } } }
EventFrame → { "event": "session.user_interrupt", "payload": { "session_id": "ab12" } }
Daemon 监听的 WebSocket 端口只接受 Origin: chrome-extension://[a-p]{32} 的连接,连接建立后第一帧必须是 system.handshake,有 5 秒超时,握手失败立即断开。
三、核心原理:如何复用真实登录态
这是 BrowserSkill 最核心的差异化设计,答案出乎意料地简单,却是其他工具难以轻易复制的护城河。
3.1 根本原因:Chrome Extension 运行在同一 Profile
BrowserSkill 以 Chrome 扩展(MV3) 身份运行。扩展天然与用户的浏览器同属同一个 Chrome Profile,意味着:
Chrome Profile(一个用户身份)
├── Cookie Store(全 Profile 共享)
├── localStorage / IndexedDB(按域隔离,Profile 内共享)
├── 用户窗口 A(已登录 GitHub、企业微信、腾讯云…)
├── 用户窗口 B
└── Agent Window ← 扩展用 chrome.windows.create() 创建,同一 Profile!
当扩展执行 chrome.windows.create({ type: "normal", url: "about:blank" }) 时,Chrome 在同一个进程、同一个 Profile 里新建一个普通窗口。没有新的 Cookie Jar,没有独立的 Storage,一切存储都和用户已有的窗口共享。
对比 Playwright 的做法:playwright.chromium.launch() 会启动一个全新的 Chrome 进程,使用一个临时空白的 userDataDir,所有 Cookie 都不存在,必须重新走登录流程。
3.2 核心代码:Agent Window 的创建
来自 apps/extension/src/session-manager/agent-window.ts:
export const chromeAgentWindowApi: AgentWindowApi = {
async create(url: string, opts: AgentWindowCreateOptions = {}): Promise<number> {
const win = await chrome.windows.create({
type: "normal",
focused: opts.focused ?? true, // --no-focus 时传 false
url, // 从 "about:blank" 开始
...(opts.size ? { width: opts.size.width, height: opts.size.height } : {}),
});
if (typeof win?.id !== "number") {
throw new Error("[bh] chrome.windows.create returned no window id");
}
return win.id;
},
// ...
};
这就是全部——一个普通的 chrome.windows.create() 调用。Agent Window 的 URL 从 about:blank 开始,而非真实页面,原因在代码注释里写明:chrome:// 页面(包括新标签页)会拒绝 Page.navigate CDP 命令,所以用 about:blank 作为启动 URL。
3.3 核心代码:CDP 接入方式
扩展使用 chrome.debugger 权限,不需要开启远程调试端口,直接按 tabId 在 Profile 内附加。
来自 apps/extension/src/browser-driver/chromium-cdp.ts:
async ensureAttached(tabId: number): Promise<void> {
if (this.attachedTabs.has(tabId)) return;
// 防止并发 attach 同一 tab
const existing = this.attachInFlight.get(tabId);
if (existing) { await existing; return; }
const attach = (async () => {
// 直接用 tabId,同 Profile 内的 tab,无需远程调试端口
await this.api.attach({ tabId }, CDP_PROTOCOL_VERSION); // "1.3"
try {
await this.enablePageDomain(tabId); // Page.enable
await this.enableConsoleDomains(tabId); // Runtime.enable + Log.enable
await this.enableNetworkDomainBestEffort(tabId); // Network.enable
this.attachedTabs.add(tabId);
} catch (err) {
// 关键:domain enable 失败后必须回滚 attach,否则 tab 永久卡死
// "Another debugger is already attached" 错误将永远触发
await this.api.detach({ tabId }).catch(() => {});
throw err;
}
})().finally(() => { this.attachInFlight.delete(tabId); });
this.attachInFlight.set(tabId, attach);
await attach;
}
一旦附加成功,所有操作通过 sendCommand 发出:
async send<T = unknown>(tabId: number, method: string, params?: object): Promise<T> {
if (!this.attachedTabs.has(tabId)) await this.ensureAttached(tabId);
const result = await this.api.sendCommand({ tabId }, method, params ?? {});
return result as T;
}
操作已登录的 Tab 时,它的 Cookie 状态完全不受影响,CDP 命令只是在已有页面上执行鼠标/键盘事件或导航,等同于用户本人在操作。
3.4 Tab Borrow:直接挪用用户已登录的 Tab
除了在 Agent Window 自主导航,BrowserSkill 还支持将用户已经打开的、已登录的 Tab 直接借入 Agent Window 使用。
来自 apps/extension/src/tools/tabs.ts,核心逻辑在 moveTabForBorrow:
async function moveTabForBorrow(
tabsApi: TabMutationApi,
tabId: number,
agentWindowId: number,
originalWindowId: number,
originalIndex: number,
signal?: AbortSignal,
): Promise<RpcError | null> {
try {
// 关键:只是把 Tab 从用户窗口移到 Agent Window
// Tab 的 DOM / Cookie / JS 运行时 / XHR 连接全部原样保留
await tabsApi.move(tabId, { windowId: agentWindowId, index: -1 });
} catch (err) {
return { code: "cdp_failed", message: `chrome.tabs.move failed: ...` };
}
if (aborted(signal, "tab_borrow")) {
// 被取消:立即回滚,把 Tab 移回原处
await tabsApi.move(tabId, { windowId: originalWindowId, index: originalIndex });
return { code: "cancelled", message: "tab_borrow aborted" };
}
return null;
}
chrome.tabs.move() 只是改变了 Tab 所属的窗口,Tab 本身的状态(DOM 树、JS 堆、网络连接、Cookie)一个字节都不会变。Agent 拿到这个 Tab 后用 CDP 直接操作,等于在用户已经登录的页面上继续操作。
Borrow 流程完成后,tab_return 把 Tab 移回原始窗口(或当原始窗口已关闭时找到备用窗口):
// 归还时,原始窗口已关闭 → 找到最后聚焦的普通窗口作为备用
// 关键:必须排除所有 Session 的 Agent Window,防止跨会话写权限提升(PR #3 修复)
if (lastId !== null && lastId !== ctx.agentWindowId && !isAgentWindowId(lastId)) {
return { windowId: lastId, index: -1 };
}
3.5 两种路径的完整流程
路径 1:Agent Window 自主导航(最常用)
─────────────────────────────────────────
bsk session start
→ chrome.windows.create() → Agent Window(同 Profile,天然有 Cookie)
bsk navigate --session ab12 --url https://corp.com
→ CDP Page.navigate
→ 服务端看到用户已登录的 Session → 直接进入主页,无需登录
路径 2:借用用户已打开的 Tab(精确复用已有状态)
─────────────────────────────────────────
bsk tab_list --session ab12 --scope user
→ 列出用户所有普通窗口里的 Tab(含 URL、标题)
bsk tab_borrow --session ab12 --tab-id 42
→ 弹出 OS 通知 + 页面叠加层(5 秒倒计时确认)
→ 用户点 Allow → chrome.tabs.move(42, agentWindowId)
→ Tab 携带完整登录状态、DOM、JS 上下文进入 Agent Window
bsk snapshot / click / fill ...
→ CDP 操作这个已登录的 Tab
bsk tab_return --session ab12 --tab-id 42
→ chrome.tabs.move(42, originalWindowId)
→ Tab 归还,状态完整,用户可继续正常使用
四、VOM —— 专为 LLM 设计的语义观察层
4.1 原始 AX Tree 的问题
大多数浏览器自动化工具直接把 Chrome 的 Accessibility Tree 转给 LLM。AX Tree 是为无障碍辅助技术设计的,不是为 LLM 设计的,存在以下问题:
- 重复标签无法区分:表格里有 50 个 “查看” 按钮,AX Tree 全部标注为
button "查看",LLM 不知道该点哪个 - 标签与控件绑定不稳定:
StaticText "验证码:"和后面的input在 AX Tree 里是兄弟节点,不是父子关系,LLM 要自己推断哪个 input 对应这个 label - 自定义控件漏识别:很多用
div+ JS 实现的下拉菜单、日期选择器,AX Tree 要么不标记为可操作,要么 role 信息不准确 - 表单状态混淆:placeholder 文字在 AX Tree 里和真实内容没有区分,LLM 容易以为表单已经填了内容
- hover-only 元素不可见:很多导航菜单只在 hover 时展开,静态 AX Tree 里根本看不到这些选项
4.2 VOM 的核心设计
VOM(Virtual Object Model)是 BrowserSkill 自研的语义观察层,位于 packages/vom/,核心思想是融合多源信息,产出任务导向的语义视图。
输入来源:
chrome.accessibility.getTree() → AX 语义结构(role/name/value)
CDP Accessibility.getFullAXTree → backendNodeId 映射(用于后续 CDP 操作)
CDP DOM.captureSnapshot → 真实 DOM 结构 + 元素坐标
Runtime.evaluate(formState) → 表单运行时状态(是否填写、实际值)
关键处理步骤:
- 上下文消歧:为重复弱标签(如多个 “查看”)补充局部上下文——找到最近的区分性祖先节点(如行首的名称或 ID),附加到标签前
- 表单状态分类:区分
[empty](从未填写)/[filled](用户输入)/[default](有默认值但可修改),让 LLM 知道哪些字段需要填写 - 冗余压缩:移除已被 cell/group name 覆盖的重复文本子节点,减少 Token 消耗
- 自定义控件恢复:识别非原生但实际可操作的控件(如
div[role=button]+tabindex),补全可操作性标注 - Surface Probe:对可能有 hover-triggered 内容的元素,发起受控的 hover 探测,收集展开后新出现的节点,附加到父节点描述中
@eN 引用系统:每次 snapshot 或 observe 调用,所有可操作节点按顺序编号为 @e1、@e2……LLM 使用这些短引用来指代操作目标(bsk click @e3),比 CSS 选择器短得多,也比 XPath 稳定得多。每次重新 snapshot 后引用重新编号,确保引用总是指向当前页面真实存在的元素。
VOM 输出示例(LLM 实际看到的内容):
nav @e1
a[href=/dashboard] @e2 "主页"
a[href=/settings] @e3 "设置"
a[href=/logout] @e4 "退出"
main @e5
h1 "用户管理"
table @e6
thead: 姓名 | 部门 | 操作
tr[row=1] "张三 | 技术部"
button @e7 "查看" ← 上下文消歧:行首名称已附加
button @e8 "编辑"
tr[row=2] "李四 | 产品部"
button @e9 "查看"
button @e10 "编辑"
form @e11
input[text][empty] @e12 placeholder="搜索用户名..."
button @e13 "搜索"
相比原始 AX Tree,这个视图:压缩了 60%+ 的 Token、消除了重复标签歧义、明确了表单状态、保留了操作所需的所有语义信息。
4.3 snapshot 与 observe 的区别
| 命令 | Effect 分类 | 是否触发 hover 探测 | 适用场景 |
|---|---|---|---|
bsk snapshot | PassiveRead(只读,不受 Stop 门控) | ❌ 不触发 | 快速获取当前页面静态状态 |
bsk observe | TransientInput(受用户中断门控) | ✅ 受控探测 | 有下拉菜单、tooltip、hover-only 导航的复杂页面 |
observe 的 hover 探测有边界限制,不会无限递归展开——它对每个候选节点做一次 hover,收集新出现的节点后停止,避免副作用过大。
五、安全设计
5.1 Fail-Closed 原则
BrowserSkill 在所有授权路径上严格遵循 fail-closed(不确定则拒绝)原则,这是项目开源第一周就集中修复的核心问题。
PR #5(fix: don't fail-open the borrow gate on a transient tabs.get error)修复了一个典型 fail-open 漏洞:当 tabs.get() 因 Service Worker 被系统回收而抛出 transient 错误时,原代码把异常 catch 后继续执行了借用操作。修复后,任何错误都返回拒绝。
PR #16(fix: fail closed on borrow confirmation)修复了确认流程的多个 fail-open 路径,包括:sendMessage 超时、content script 未加载、response 格式异常等情况,全部改为返回 false(拒绝)。
当前代码 borrow-confirmation.ts 中,所有异常路径均如此处理:
const settle = (allowed: boolean) => { /* ... */ resolve(allowed); };
// 超时
setTimeout(() => { dismissPendingOverlay(); settle(false); }, BACKGROUND_TIMEOUT_MS);
// abort
const onAbort = () => { dismissPendingOverlay(); settle(false); };
// sendMessage 失败 → 尝试下一个候选窗口,全部失败 → 等待 OS 通知按钮或超时 fail-closed
5.2 跨会话隔离
每个 Session 有独立的 Agent Window。PR #3 修复了一个严重的跨会话安全漏洞:
漏洞场景:Session A 借用的 Tab,原始窗口被用户关闭后需要归还到备用窗口。chooseFallbackWindow 调用 getLastFocused({ windowTypes: ["normal"] }) 获取最近聚焦的窗口,但 Agent Window 本身也是 type: "normal",若 Session B 的 Agent Window 恰好是最后聚焦的窗口(这种情况很常见,因为 Agent Window 会主动抢占焦点),用户的 Tab 就会被错误移入 Session B 的 Agent Window。
影响:Session B 的所有写操作(click/fill/evaluate)会通过 Agent Window 的归属校验,可以对这个用户从未授权给 Session B 的 Tab 执行操作。Session B 关闭时会销毁这个 Tab,用户的数据静默丢失。
修复:注入 isAgentWindowId 谓词,在所有窗口选择逻辑中排除所有活跃会话的 Agent Window,而非仅当前会话的:
isAgentWindowId: (windowId) => manager.findByWindowId(windowId) !== null
5.3 用户中断门控(编译时类型安全)
每个 RPC 方法在 crates/bsk-protocol/src/method.rs 中被 Rust 枚举 MethodEffect 强制分类:
pub enum MethodEffect {
BrowserMutation, // 写操作:click/fill/navigate/press/select/evaluate...
TransientInput, // 临时输入:hover/observe(可以被 Stop,但不影响页面状态)
PassiveRead, // 只读:snapshot/screenshot/get_html(永远不被 Stop)
ControlPlane, // 控制面:session_stop/session_list(永远不被 Stop)
}
effect() 方法使用 match 穷举所有变量,Rust 编译器强制保证每个新增方法都必须分类,遗漏分类会导致编译失败。
用户在 Agent Window 点击 Stop 按钮后:
- content script 发送
session.user_interrupt事件 → background → WS → Daemon - Daemon 标记该 Session 的中断信号
- 下一个
BrowserMutation操作在进入队列前命中门控,立即返回UserAborted PassiveRead和ControlPlane操作不受影响,session stop可以正常执行
5.4 会话生命周期中的 session stop 时序
六、Daemon 内部设计
6.1 每会话串行队列
Daemon 对每个 Session 维护一个容量为 64 的串行队列(daemon/queue.rs),同一 Session 的工具调用严格串行执行,不同 Session 可以并行。
为什么需要串行? 扩展侧维护一个 ref-store:每次 snapshot/observe 产生的 @eN → { backendNodeId, tabId } 映射。如果两个调用并发执行,一个 snapshot 的 @e1 可能被另一个 snapshot 的新编号覆盖,导致 click @e1 操作到错误的元素。串行队列从根本上消除了这个竞争条件。
Session ab12 的队列: [observe] → [click @e3] → [fill @e7 "hello"] → ...
↑ 正在执行 ↑ 等待 ↑ 等待
Session cd34 的队列: [navigate] → [snapshot] → ...
↑ 并行执行
6.2 自动更新系统
Daemon 每 30 分钟做一次版本检查(update/check.rs)。PR #76 修复了周期任务未被实际调度的 bug,PR #77 在此基础上实现了自动升级:
检查到新版本
→ 下载 release archive(reqwest)
→ SHA256 校验(PR #38 加入)
→ 解压到临时路径
→ 检查是否有活跃 Session
→ 有活跃 Session → 推迟到下一个 tick(30 分钟后)
→ 无活跃 Session
→ Unix:原子替换二进制 → spawn 分离子进程等待旧进程退出 → 旧 daemon 优雅退出
→ Windows:暂存替换包,记录日志(exe 运行中无法原地覆盖,PR #85 修复)
可通过 BSK_AUTO_UPDATE=off 禁用自动升级,退回到仅提示模式。
6.3 MV3 Service Worker 保活
Chrome MV3 扩展的 Service Worker 在无活动时会被系统回收(通常 30 秒内),这是 BrowserSkill 稳定性的最大挑战之一。项目通过两层机制保活:
- 30 秒 Keepalive Alarm(
lib/keepalive.ts):每 30 秒触发一个 Chrome Alarm,唤醒 Service Worker,检查 WebSocket 是否还连着,不连就重连 - 20 秒 Heartbeat(
lib/heartbeat.ts):握手成功后,Extension 每 20 秒向 Daemon 发一个system.heartbeat帧,通过 WS 活动阻止浏览器判定 SW 为空闲
此外,chrome.runtime.onStartup 和 chrome.idle.onStateChanged 事件在系统休眠唤醒后触发重连,不依赖 Alarm 恰好命中。
七、PR 全景分析:项目演进路线
自 2026 年 7 月 6 日开源,项目累计 88 个 PR(约 50 个已合并),主要贡献者为 BB-fat(主力协作者)和社区开发者 NianJiuZst、hobostay、shnpd、Ljy-0827、iuyo5678、hjxccc 等。
7.1 第一阶段:安全底线(7 月 6~18 日,PR #3~#16)
开源第一周,核心工作是修复可能导致跨会话写权限提升的安全漏洞,几乎都是 hobostay 提交:
| PR | 标题 | 解决的问题 |
|---|---|---|
| #3 | never return a borrowed tab into another session’s Agent Window | Tab 归还时 fallback 窗口选择不排除其他会话的 Agent Window,导致跨会话写权限提升 |
| #4 | reject cross-browser session events from non-owners | Daemon 未校验 WS 消息的 browser_instance_id 归属,任何扩展实例可发送他人会话的控制事件 |
| #5 | don’t fail-open the borrow gate on a transient tabs.get error | tabs.get() transient 错误被 catch 后静默放行了借用操作 |
| #16 | fail closed on borrow confirmation | 确认流程多个错误路径 fail-open |
这一阶段的模式非常清晰:在任何授权决策点,错误必须拒绝而非放行。这几个 PR 的补丁都极小,但影响极大——如果不修,任何 Agent 都可以通过时序竞争操作到不属于自己 Session 的用户 Tab。
7.2 第二阶段:能力建设(7 月 7~31 日,PR #7~#53)
修完安全底线后,开始大规模功能扩展:
调试能力
- PR #7(
bsk console):读取 Agent Window 的 JS 控制台输出。Agent 在页面上执行操作后如果出错,之前只能靠 snapshot 猜,现在可以直接读 console.error 定位问题 - PR #8(
bsk network):读取 XHR/Fetch 请求记录。对于需要监控 API 调用结果的场景(如提交表单后检查后端返回)非常有用
录制能力
- PR #28(
bsk record):录制用户操作为语义trace.json。这不是屏幕录制,而是语义录制——每一步的 role/name/tag 等语义描述都保留,坐标不保留,密码字段自动脱敏。Agent 可以读取这个 trace 然后重放,也可以作为 few-shot 示例告诉 LLM 如何操作某个系统
无人值守模式
- PR #53(
BSK_REQUEST_HELP=off):request-help是阻塞式的人机交互命令,在 CI/CD 流水线或后台服务器上没有人工操作者。设置此环境变量后,命令立即返回{"outcome": "disabled"},Agent 收到后自主决策是否继续,而不是永久挂起等待
观察层重构
- PR #45(VOM 语义层):这是这一阶段最重大的变更,引入了整个 VOM 语义层体系,详见第四章
7.3 第三阶段:打磨与扩展(8 月 4~13 日,PR #52~#87)
能力补全
| PR | 新增能力 | 解决的场景 |
|---|---|---|
| #52 | Agent Window 尺寸控制 | 响应式布局测试、特定分辨率下的操作 |
| #60 | 移动设备模拟(bsk emulate) | 移动 H5 页面的 Agent 自动化,含 UA/viewport/触控三重模拟,7 种内置设备预设 |
| #68 | Hover 端到端支持 | Agent 遇到 hover-only 导航菜单时不再卡住,observe → hover @ref → observe → click 完整路径 |
| #81 | Popup 隐藏控制提示开关 | 用户觉得 Agent Window 里显示的控制 overlay 视觉干扰大,可在扩展 Popup 里关掉 |
| #87 | --no-focus(无焦点会话启动) | Agent 后台执行任务时不抢占用户当前焦点,社区 Issue #26 反映的实际使用痛点 |
稳定性修复
| PR | 修复内容 |
|---|---|
| #33 | CDP domain enable 失败后必须回滚 attach,否则 tab 永久卡在"另一个 debugger 已附加"状态 |
| #34 | wait_until=commit 在 readyState probe 失败时无法正确解析 |
| #39 | Console domain enable 失败后被错误标记为"已启用",导致实际上没有抓到任何日志 |
| #64 | screenshot 时叠加层(Control / Record overlay)会出现在截图里,现在截图前临时隐藏 |
| #82 | WS 重连生命周期加固,解决 MV3 Service Worker 回收后重连不稳定的问题 |
| #83 | captureVisibleTab 在某些情况下失败,回退到 CDP Page.captureScreenshot |
自动更新系统完善(详见第六章):PR #76 修复周期检查未触发 → PR #77 实现自动升级 → PR #85 修复 Windows staged 替换 → PR #86 修复 Windows Named Pipe 超长路径
生态接入:PR #65 新增 Kimi Code harness 支持,bsk install-skill 现支持 12 种 Agent Harness 自动部署
7.4 未合并但有价值的 PR(社区贡献与方向探索)
| PR | 标题 | 未合并原因 | 价值 |
|---|---|---|---|
| #54 | support unfocused agent windows | 与 #52(窗口尺寸)的参数位置冲突,被 BB-fat 重构为 #87 后合并 | 体现社区在推动非阻塞会话 |
| #51 | agent self-recovery when 0 browsers connected | 浏览器未运行时自动拉起 Chrome | 无人值守场景的重要能力,尚未合并 |
| #49 | Make recording overlay draggable | 录制叠加层可拖动,避免遮挡页面控件 | 实用的 UX 改进 |
| #47 | 添加控制覆盖层可见性切换 | 功能与 #81 重叠,#81 从 Popup 层面解决了同样的需求 | — |
八、完整工具命令速查
BrowserSkill CLI 共 28 个子命令,按 Effect 分类:
只读命令(PassiveRead)——不受用户 Stop 门控,永远可以执行:
bsk snapshot --session <id> # 获取当前页面的 VOM 语义快照
bsk observe --session <id> # 语义快照 + hover 探测(发现隐藏菜单)
bsk get-html --session <id> # 获取原始 HTML
bsk screenshot --session <id> # 截图(PNG)
bsk console --session <id> [--since <n>] # 读取 JS 控制台输出
bsk network --session <id> [--since <n>] # 读取网络请求记录
bsk tab list --session <id> [--scope user|agent|all]
bsk status # 检查 daemon 和扩展连接状态
bsk doctor # 诊断环境(退出码非 0 表示有问题)
bsk browsers # 列出已连接的浏览器实例
bsk logs [--tail <n>] # 查看 daemon 日志
写操作命令(BrowserMutation)——用户按 Stop 后立即拒绝:
bsk navigate --session <id> --url <url>
bsk navigate-back --session <id>
bsk navigate-forward --session <id>
bsk reload --session <id>
bsk click --session <id> --ref <@eN>
bsk hover --session <id> --ref <@eN>
bsk fill --session <id> --ref <@eN> --value <text>
bsk press --session <id> --ref <@eN> --key <Enter|Tab|...>
bsk select --session <id> --ref <@eN> --value <option>
bsk evaluate --session <id> --expression <js>
bsk tab create --session <id> [--url <url>]
bsk tab close --session <id> --tab-id <n>
bsk tab select --session <id> --tab-id <n>
bsk tab borrow --session <id> --tab-id <n>
bsk tab return --session <id> --tab-id <n>
bsk window resize --session <id> --width <n> --height <n>
bsk emulate --session <id> --device <iphone-14|pixel-7|...> | --off
bsk request-help --session <id> --prompt <text> [--target <@eN>] [--timeout <5m>]
bsk wait-for-navigation --session <id>
bsk wait-ms --session <id> --ms <n>
控制面命令(ControlPlane)——不受 Stop 门控,即使用户中断也可执行:
bsk session start [--browser <id>] [--no-focus] [--width <n>] [--height <n>]
bsk session stop <session-id>
bsk session list
bsk record start --session <id>
bsk record stop --session <id>
bsk install-skill [--harness <name>] [--all]
bsk update [--check]
bsk daemon [start|stop|status]
九、设计哲学总结
9.1 安全优先:Fail-Closed
在任何授权路径上,错误状态必须拒绝而非放行。这不只是代码规范,而是系统设计的第一原则,体现在编译时的 MethodEffect 强制分类、所有 Borrow 确认的超时 fail-closed、Tab 归还目标的多重 Agent Window 排除等每一个细节上。
9.2 LLM-Native 观察层
VOM 不是给人类工程师调试用的,是专门为 LLM 的 Token 效率和理解能力设计的。这意味着:不保留人类可以脑补的冗余信息,明确标注 LLM 无法自行推断的状态(表单是否填写),主动发现 LLM 无法感知的隐藏元素(hover-only 菜单)。这是从第一性原理重新设计「AI 如何感知网页」。
9.3 Shell-First = 生态开放
bsk 是纯 CLI 工具,任何支持 Shell 的 Agent Harness 都能接入,不依赖特定 AI SDK 或 MCP 协议。bsk install-skill 自动检测本机安装的 Harness 并写入 SKILL.md,让 LLM 在下次启动时自动知道如何使用 bsk。目前支持 12 种 harness,这是一个生态锁定策略——接入越多,替换成本越高,网络效应越强。
9.4 产品的核心护城河
BrowserSkill 的差异化不在于技术复杂度(核心机制"Chrome 扩展 + 同一 Profile"并不复杂),而在于它是第一个把这个机制产品化、系统化、安全化的开源项目:
- 解决了所有别人没解决的边界情况:跨会话隔离、用户中断、Session 清理、CDP 附加回滚
- 为 LLM 专门重新设计了观察格式(VOM),而不是把现有 API 的输出直接喂给 LLM
- Human-in-loop 是一等公民,而不是事后补丁
- 真实浏览器意味着可以访问一切需要登录的内网系统、企业 SaaS、个人账号,这是测试账号永远无法完全替代的
更多推荐


所有评论(0)