让AI Agent不乱跑、不抢焦点:一次Browser Use改造全记录

1 评论 352 浏览 1 收藏 27 分钟

给AI Agent装上浏览器,从Demo到可用产品,远不止“能打开网页”那么简单。本文作者通过为Codex配置Browser Use,解决了权限隔离、焦点抢占、结果可观察等真实痛点,总结出一套本地浏览器Agent的工作协议与产品原则,对构建可靠AI工具极具参考价值。

我给 Codex 装上 Browser Use,本来只想解决一个小问题:稳定读取闲鱼短链接。

安装完成后,我先把它从日常 Chrome 中隔离;接着又处理浏览器抢焦点、并发标签分配、任务结束页面消失、容量满载回收。最后留下的是一套本地浏览器 Agent 工作协议,比“更会点网页”多得多。

这段经历很像一个产品从 Demo 走向可用状态的过程。Demo 只需要证明“能做”;真正的产品还要回答:它能看见什么,会不会打扰用户,用户怎么知道它做过什么,资源不够时会牺牲谁。

一、表层需求是打开链接,底层需求是建立可信执行环境

事情从同一个闲鱼短链接的两种结论开始。

一个会话说链接失效,另一个会话用真实浏览器完成跳转并读到商品页。差异不在推理能力,而在执行环境:HTTP 首次请求失败,不代表浏览器最终无法打开。

因此,“稳定读取网页”至少包含四层:

  1. 能让页面真实运行,而不是只抓一次响应;
  2. 能保留登录和跳转上下文;
  3. 能把结果留给用户检查;
  4. 能限制 Agent 的可见范围。

Browser Use 解决了前两层,后两层需要本机产品化设计。

二、权限边界:方便复用登录,不等于默认接管全部 Chrome

Browser Harness 可以连接用户正在使用的真实 Chrome,继承登录状态、扩展、历史和书签。这对一次性的协助任务很省事。

但我的使用方式是让多个 Codex 会话长时间处理网页,同时我继续使用 Chrome。此时“继承现有登录”与“可见整个 Profile”不能视为同一件事。

新开一个窗口并没有形成权限隔离。只要仍在同一个 Profile、同一个浏览器进程和同一个 CDP 边界里,Agent 仍可能发现其他窗口。

最终方案使用独立 Chrome 进程、独立 Profile、固定 CDP 端口和按会话区分的 daemon。

产品启示很直接:浏览器 Agent 的默认权限应该是“完成任务所需的最小环境”,而不是“用户已经登录,所以全部交给它”。

三、打扰成本:后台运行不是把窗口藏起来

隔离之后,第二个问题出现了:专用 Chrome 每次创建标签都会前置,抢走用户正在使用的应用焦点。

从系统角度看,任务成功了;从用户体验看,它不可用。

我尝试过后台启动浏览器、创建页面后恢复原应用。这些办法只能遮住启动瞬间。只要后续任务继续按需创建或激活新页面,打扰仍会反复发生。

最后采用的是预分配:Chrome 冷启动时一次准备 12 个标签槽位,工作过程中只认领已有槽位并在后台导航。

12 不是每次任务消耗 12 个标签,而是容量上限。一个会话可以占一个,也可以因为对照任务占多个;未使用的槽位保持空闲。

这其实是一个典型的体验与资源交换:预先占用少量可见资源,换取后续操作不打断用户。数字 12 不是通用答案,稳定的容量模型才是。

四、可观察性:任务完成不等于清理现场

第一版标签池把“任务结束”理解为“释放资源”。标签会被重置为 about:blank。

于是 Agent 说任务完成,我打开专用 Chrome,只看见一排空白页。

对后台系统来说,页面只是可复用资源;对用户来说,页面还是结果、证据和继续操作的入口。

因此,我把标签状态改成:

空闲:可以被分配

运行:正在使用,禁止回收

完成:保留真实页面,允许用户检查,也允许以后回收

正常完成只改变状态,不销毁页面。只有用户明确清理时,才关闭并抹掉内容。

这个设计解决了三个问题:

  1. 用户能看见 Agent 实际打开了什么;
  2. 用户能手动接管验证码、付款或最终发送;
  3. 系统仍然知道哪些页面可以在容量不足时回收。

五、异常策略:第 13 个任务应该失败,还是伤害旧任务?

固定容量必然会满。

标签池的规则是:有空闲就使用;没有空闲就回收最早完成的标签。跨会话回收前,还要确认旧会话全部完成、停止旧 daemon、清空旧 DOM。

正在运行的标签永远不自动回收。如果 12 个都在运行,第 13 个请求会失败并说明容量已满。

这是一次有意选择的“安全失败”。

很多自动化产品会把“尽可能继续跑”当成稳定性。但如果继续的代价是销毁另一个运行任务,那只是把明确错误换成了隐蔽错误。对 Agent 系统而言,可解释地停下通常比无声地破坏更可靠。

六、显式接管:Agent 和人怎么共用专用浏览器

默认情况下,任务只在后台导航。用户明确要求查看时,才执行 show_tab(),让专用 Chrome 前置。

页面上需要密码、验证码、授权、付款或最终发送时,Agent 停下来交给人。手动检查结束后,如果还要让 Agent 继续操作一个已完成标签,先把它恢复为运行状态。

我更关心两条具体规则:

  1. 谁在操作页面,状态必须可见;
  2. 哪些动作必须交回用户,不能靠猜。

七、验收方式:不要只测成功路径

最终验收包含这些场景:

  • 冷启动严格创建 12 个槽位,没有额外空白页;
  • 启动前后台应用不变;
  • 同一会话可以认领多个标签;
  • 10 个运行加 2 个完成时,新请求只回收完成项;
  • 跨会话回收前停止旧 daemon,旧 DOM 不残留;
  • 12 个全运行时,第 13 个请求安全失败;
  • 只有显式展示才允许浏览器前置;
  • 隔离环境看不到日常 Chrome 的标签。

如果只测试“能打开网页”,权限泄漏、抢焦点、页面消失和错误回收都不会暴露。

八、从这次改造得到的五条产品原则

  1. 把执行成功与用户可用分开验收。
  2. 登录复用是能力,权限最小化是默认策略。
  3. 后台任务要把打扰成本计入成功标准。
  4. 完成结果应该可检查,清理是另一个显式动作。
  5. 容量不足时优先安全失败,不破坏运行中的工作。

Browser Use Cloud 适合并发、服务器、代理和反风控;独立本地 Chrome 适合固定账号、个人使用和长期复用登录。我的选择是后者,因为当前主要处理闲鱼、淘宝等需要人工登录的网站。

这套方案仍有维护成本:Browser Use 或 Browser Harness 升级后,本机补丁可能被覆盖,需要重跑冷启动、标签数量和焦点测试。

浏览器 Agent 从 Demo 走向日常工具,通常要先补齐边界、状态和失败方式,然后再谈更多动作。

九、发布也是产品交付:后台成功不等于读者可用

技术方案通过以后,我把完整过程整理成文章,并同步到知乎、人人都是产品经理、简书、小红书、抖音和哔哩哔哩。发布阶段出现的问题,和前面的浏览器改造几乎同构。

作者后台显示正文,不代表公开页没有丢图;内容包里存在 MD 文件,不代表读者能访问;评论已经提交,不代表未登录用户能看到;平台显示“提交成功”,也可能只是进入审核。

因此发布流程也需要一套外部验收:

  1. 用公开链接而不是编辑器地址检查文章;
  2. 确认正文、图片、代码块和提示词末句都存在;
  3. 分段评论必须编号连续;
  4. 审核中、已发布、待补充要分开记录;
  5. 长提示词不能只引用本地文件,也不能只依赖评论区。

本次 6322 字提示词在长文平台直接内嵌,在小红书排成 6 张连续高清文字图,供读者截图或识别文字。这样做并不华丽,但交付路径是完整的。

从产品角度看,整条链路应该是:需求沟通、方案修改、边界测试、用户验收、内容冻结、经验文章、视觉和视频、分平台发布、公开页复核。任何一步只有内部状态,没有外部可验证结果,都不能算真正完成。

可直接交给 Codex 的执行提示词

这是明确执行授权:请直接在当前 Mac 上完成 Browser Use / Browser Harness 的检查、安装或修复、Skill 去重、独立 Chrome 配置、后台可观察标签池和验收。

普通、可逆、低风险的本机检查与配置不需要重复问我。涉及以下动作时必须停下来让我处理或确认:Chrome 的“允许远程调试”安全弹窗、账号密码、验证码、MFA、授权、付款、最终发送、公开发布、删除非重复文件、覆盖我已有的不同配置。

最终目标

1. Browser Use 只连接专用 Chrome,不读取或操作我的日常 Chrome。

2. 专用 Chrome 使用独立进程、独立 Profile、固定 CDP 端口和按 Codex 会话区分的 daemon。

3. 浏览器默认在后台启动和工作,不抢当前前台应用。

4. 冷启动时准备 12 个可观察标签槽位,任务按需认领;不是每个任务固定占用 12 个。

5. 同一 Codex 会话可以认领多个标签,不同会话有清晰所有权。

6. 标签有“空闲 / 运行 / 完成”三种可见状态。

7. 正常任务结束后保留真实页面,用户能切过去检查;完成不等于销毁。

8. 容量满时只回收最早完成且可安全回收的标签,绝不自动关闭运行中的标签。

9. 12 个标签全部运行时,第 13 个请求必须安全失败并报告容量已满。

10. 只有我明确说“显示页面 / 把标签切到前台”时,才允许专用 Chrome 前置。

11. 不配置 Browser Use Cloud,不注册云账号,不产生云端费用。

一、先读取规则与检查现状

– 读取当前 Browser Use / Browser Harness 官方安装文档和本机已安装的 SKILL.md。

– 检查 macOS 版本、CPU 架构、Chrome、Python、uv、browser-use、browser-harness、CLI 路径和当前 daemon。

– 检查是否已经存在以下内容或等价配置,不要重复追加:

– 专用 Profile;

– 隔离启动器;

– 本机 Skill 策略;

– 标签池状态文件;

– 自定义 agent_helpers.py;

– 包目录补丁和备份。

– 检查工作树或目标文件是否有用户修改。任何覆盖前先做差异比较和带时间戳备份。

– 先报告检查结果和预计改动清单,再继续执行;不需要因为普通低风险步骤等待我的第二次确认。

二、安装或修复

– 如果尚未安装,优先用 uv 管理独立 Python 3.12 工具环境,不修改系统 Python。

– 安装当前官方推荐的 browser-use / browser-harness CLI、需要的浏览器组件和 Codex Skill。

– 以当前官方文档和实际 CLI 帮助为准,不使用过时命令。

– 安装后必须运行 doctor,并打开 https://example.com,读取 URL、标题和 h1。不能只根据包管理器返回成功就宣布完成。

三、Skill 去重

检查:

~/.codex/skills/browser-use/SKILL.md

~/.agents/skills/browser-use/SKILL.md

– 只有文件大小、SHA-256 和逐字节比较均完全一致时,才把它们视为重复入口。

– 完全相同时保留 ~/.codex/skills/browser-use,删除且仅删除 ~/.agents/skills/browser-use。

– 如果内容不同,禁止删除,报告差异并保留两份。

– 不卸载 Browser Use,不删除其他 Skill。

四、建立独立 Chrome

– 专用 Profile 默认使用:

~/.browser-use/profiles/codex-isolated

– 默认 CDP 端口使用 9223。先检查是否被非目标进程占用;如需改端口,在启动器、Skill、状态和测试里保持一致。

– 启动 Chrome 时必须使用非默认 user-data-dir,参数至少包含:

–user-data-dir=<专用 Profile 的绝对路径>

–remote-debugging-port=<端口>

–no-first-run

–no-default-browser-check

– 不复制日常 Chrome Profile,不读取日常标签、Cookie、密码、Token、历史或本地存储。

– 使用后台启动方式,并记录启动前的前台应用;启动或冷启动引导结束后恢复原前台应用。

– 若 Chrome 版本需要远程调试授权,打开正确的 chrome://inspect/#remote-debugging 页面并停下来让我点击 Allow。

五、创建隔离启动器

创建或合并更新:

~/.local/bin/browser-use-isolated

启动器必须:

– 使用绝对路径定位 Chrome 和真实 CLI;

– 创建专用 Profile 和 Agent Workspace;

– 固定 BU_CDP_URL 指向专用端口;

– 通过 CODEX_THREAD_ID 生成安全、长度受限的 daemon 名称;

– 等待 /json/version 和至少一个 page target 可访问;

– 对 Chrome 残留后台进程、端口存在但无 page target、Chrome 更新后 generation 变化做自愈;

– 不在存在真实未处理页面时贸然重建池;

– 通过 shell 语法检查;

– 默认不把 Chrome 带到前台。

六、实现 12 标签池

优先把任务级逻辑放在独立 Agent Workspace 的 agent_helpers.py 中,例如:

~/.config/browser-harness/agent-workspace-isolated/agent_helpers.py

不要直接改官方核心包,除非确认仅靠 Workspace Helper 和启动器无法避免 macOS 前置。必须改包目录时:

– 先定位当前版本真实实现,不依赖提示词里的旧路径或行号;

– 创建带时间戳备份;

– 做最小修改;

– 记录升级可能覆盖;

– 修改后做静态哈希和冷启动测试。

标签池要求:

– 容量固定为 12,可集中定义,避免多个文件写不同数字。

– 冷启动时一次准备 12 个 about:blank#codex-pool-XX 页面;普通任务运行期间不再用 Target.createTarget 或 Target.activateTarget 创建/激活工作标签。

– 启动完成后标签标题依次为“空闲 01”到“空闲 12”。

– 每个槽位保存 slot、targetId、owner_thread、state、claimed_at、completed_at、daemon_name 和浏览器 generation 等必要状态。

– 状态至少包括 idle、running、completed。

– 可见标题:

– idle:空闲 NN

– running: <真实页面标题或简短 URL>

– completed:✓ <真实页面标题或简短 URL>

– CDP target 返回顺序不能当成 Chrome 标签栏顺序;用显式 slot 和状态文件管理。

– new_tab(url) 每调用一次,为当前 CODEX_THREAD_ID 再认领一个槽位。

– goto_url(url) 只导航当前标签,不额外占槽位。

– switch_tab / goto_url / resume_tab 把目标状态设为 running,且只能操作当前会话拥有的标签。

– thread_tabs() 返回当前会话的全部标签、slot、targetId、URL、标题和状态。

– tab_pool_status() 返回 idle、running、completed、reclaimable、effective_available 和总容量。

七、完成、恢复与显式关闭

– 正常任务结束必须调用 complete_thread_tabs():保留真实 URL、DOM 和页面内容,只把标签改为 completed。

– 不得因为任务完成就自动调用 close_thread_tabs()。

– 已完成页面如果要继续由 Agent 操作,先调用 resume_tab() 再点击、输入、执行 JS 或截图。

– close_tab(target) 和 close_thread_tabs() 是显式破坏性清理:清掉页面内容并释放槽位。只有用户要求关闭、清理,或确认不再需要保留时才使用。

– show_tab(target) 是唯一允许显式选择标签并可能前置 Chrome 的 Helper。只有用户明确要求看页面时才调用。

八、满载与跨会话回收

new_tab(url) 的分配顺序:

1. 当前有 idle 槽位时,认领最早的空闲槽位。

2. 没有 idle 时,选择 completed_at 最早的可回收完成槽位。

3. 完成槽位属于其他会话时,只有那个会话的所有标签都已经 completed,才允许回收其中最早的槽位。

4. 转移前停止旧会话 daemon,确认不再持有目标。

5. 清空旧页面 DOM 和旧状态,再导航到新 URL,防止跨任务内容残留。

6. running 标签永远不可自动回收。

7. 12 个标签全部 running 时抛出清晰错误,报告池已满,并建议用户等待、显式清理完成项或改用 Cloud;不得关闭运行页凑容量。

状态读写要加锁并使用原子替换,防止多个 Codex 会话同时认领同一槽位。浏览器 generation 变化后,旧 targetId 不能继续使用。

九、更新 Codex Skill

在保留的 Browser Use SKILL.md 顶部合并一段本机策略,且同一策略只能出现一次:

– 本机任务必须调用 browser-use-isolated;

– 禁止自动发现日常 Chrome;

– 写明专用 Profile、CDP 地址、Agent Workspace 和 daemon 命名规则;

– 说明 12 标签池按需认领;

– 说明 idle / running / completed 三种可见状态;

– 说明正常完成保留页面;

– 说明 resume、show、close 的边界;

– 说明满载时的安全回收和全运行拒绝;

– 说明普通 browser-use 只用于 Cloud 或明确排障;

– 提醒升级可能覆盖包目录补丁,升级后必须重跑验收。

不要重写官方 Skill 的其余部分,不要重复追加同义规则。

十、验收测试

先做静态检查,再做热启动和冷启动测试。测试结束后恢复一个干净、可继续使用的池。

A. 安装与连接

– 输出 Python、browser-use、browser-harness、Chrome 版本和实际路径。

– doctor 显示专用 Chrome 可连接、daemon 正常、CDP 地址正确。

– example.com 能读取 URL、标题和 h1。

B. 隔离

– 专用 Chrome 进程参数含独立 user-data-dir 和正确端口。

– Browser Use 只能看到专用 Profile 页面。

– 不输出日常 Chrome 标签标题;只报告“可见 / 不可见”。

C. 冷启动与焦点

– 完全停止专用 Chrome 和相关 daemon 后重新启动。

– page target 总数严格为 12;池 marker 为 12;没有额外 about:blank 或 newtab 页面。

– 标题从“空闲 01”连续到“空闲 12”。

– 启动前后前台应用 PID 不变。

– 普通 new_tab / goto_url / complete_thread_tabs 流程不让 Chrome 前置。

– 显式 show_tab 测试可以前置;测试后恢复原前台应用。

D. 多标签与多会话

– 同一 CODEX_THREAD_ID 连续 new_tab 两次,必须得到两个不同 slot 和 targetId。

– 两个不同 CODEX_THREAD_ID 的运行标签不能互相 switch、resume 或关闭。

E. 可观察完成

– 打开两个真实测试页并 complete_thread_tabs。

– 页面 URL、标题和 DOM 仍然存在,标题显示 ✓。

– 用户切到专用 Chrome 时能看到真实完成页面,不是 about:blank。

– resume_tab 后状态变回 running,能继续读取页面。

F. 回收

– 构造 10 个 running 加 2 个 completed,再发起两个新标签。

– 新标签只能回收那 2 个 completed;10 个 running 的 targetId 和页面不得变化。

– 跨会话完成页被回收前,旧 daemon 已停止;旧 DOM 检查为空。

– 构造 12 个 running,再发起第 13 个,必须安全失败,12 个原页面全部保留。

G. 最终状态

– 清理测试专用页面,不清理用户要求保留的真实页面。

– 输出 tab_pool_status 和 state.json 摘要,不泄露网页私人内容。

– 若测试最终回到空池,确认 claimed=0、assignments 为空;若保留页面,逐项说明保留原因和状态。

十一、失败处理

– 第一层失败不能直接当最终结论。先诊断端口、Chrome generation、daemon、状态锁、Skill 路径、包升级覆盖和 CDP target。

– 如果修复需要覆盖用户不同配置、删除非重复文件、关闭用户真实页面或扩大到日常 Chrome,停止并向我确认。

– 不得为了通过测试而改用日常 Chrome,也不得降低“完成页面可见”“运行标签不可回收”“不抢焦点”的标准。

十二、最终报告

完成后给出:

– 系统、Chrome、Python、browser-use、browser-harness 版本;

– CLI、启动器、Profile、Agent Workspace、Skill、状态和备份的实际路径;

– CDP 端口和 daemon 命名;

– Skill 去重结果;

– 是否能看到日常 Chrome;

– 12 标签池三种状态和分配规则;

– 同一会话多标签测试结果;

– 完成页面保留测试结果;

– 10 运行 + 2 完成的回收结果;

– 12 全运行时第 13 个请求的结果;

– 冷启动标签数量、额外空白页数量和前台应用是否变化;

– show_tab 显式前置测试;

– Cloud 状态;

– 哪些文件属于本机补丁、升级后需要重测什么。

只有文件真实落盘、静态检查通过、热启动和冷启动场景均通过、日常 Chrome 不可见、正常完成页面仍可检查,才能宣布完成。

参考资料:

  • Browser Use 官方仓库
  • Browser Harness 安装与连接说明
  • Chrome 远程调试安全规则

本文由 @弗洛伊德 原创发布于人人都是产品经理。未经作者许可,禁止转载

题图来自作者提供

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 把完成页面保留下来让用户检查,这个设计太重要了。很多自动化工具只管“做完”不管“可查”,用户根本不知道Agent到底干了什么。保留现场就是保留信任。

    来自广东 回复