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

我给 Codex 装上 Browser Use,本来只想解决一个小问题:稳定读取闲鱼短链接。
安装完成后,我先把它从日常 Chrome 中隔离;接着又处理浏览器抢焦点、并发标签分配、任务结束页面消失、容量满载回收。最后留下的是一套本地浏览器 Agent 工作协议,比“更会点网页”多得多。
这段经历很像一个产品从 Demo 走向可用状态的过程。Demo 只需要证明“能做”;真正的产品还要回答:它能看见什么,会不会打扰用户,用户怎么知道它做过什么,资源不够时会牺牲谁。
一、表层需求是打开链接,底层需求是建立可信执行环境
事情从同一个闲鱼短链接的两种结论开始。
一个会话说链接失效,另一个会话用真实浏览器完成跳转并读到商品页。差异不在推理能力,而在执行环境:HTTP 首次请求失败,不代表浏览器最终无法打开。
因此,“稳定读取网页”至少包含四层:
- 能让页面真实运行,而不是只抓一次响应;
- 能保留登录和跳转上下文;
- 能把结果留给用户检查;
- 能限制 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,只看见一排空白页。
对后台系统来说,页面只是可复用资源;对用户来说,页面还是结果、证据和继续操作的入口。
因此,我把标签状态改成:
空闲:可以被分配
运行:正在使用,禁止回收
完成:保留真实页面,允许用户检查,也允许以后回收
正常完成只改变状态,不销毁页面。只有用户明确清理时,才关闭并抹掉内容。
这个设计解决了三个问题:
- 用户能看见 Agent 实际打开了什么;
- 用户能手动接管验证码、付款或最终发送;
- 系统仍然知道哪些页面可以在容量不足时回收。
五、异常策略:第 13 个任务应该失败,还是伤害旧任务?
固定容量必然会满。
标签池的规则是:有空闲就使用;没有空闲就回收最早完成的标签。跨会话回收前,还要确认旧会话全部完成、停止旧 daemon、清空旧 DOM。
正在运行的标签永远不自动回收。如果 12 个都在运行,第 13 个请求会失败并说明容量已满。
这是一次有意选择的“安全失败”。
很多自动化产品会把“尽可能继续跑”当成稳定性。但如果继续的代价是销毁另一个运行任务,那只是把明确错误换成了隐蔽错误。对 Agent 系统而言,可解释地停下通常比无声地破坏更可靠。
六、显式接管:Agent 和人怎么共用专用浏览器
默认情况下,任务只在后台导航。用户明确要求查看时,才执行 show_tab(),让专用 Chrome 前置。
页面上需要密码、验证码、授权、付款或最终发送时,Agent 停下来交给人。手动检查结束后,如果还要让 Agent 继续操作一个已完成标签,先把它恢复为运行状态。
我更关心两条具体规则:
- 谁在操作页面,状态必须可见;
- 哪些动作必须交回用户,不能靠猜。
七、验收方式:不要只测成功路径
最终验收包含这些场景:
- 冷启动严格创建 12 个槽位,没有额外空白页;
- 启动前后台应用不变;
- 同一会话可以认领多个标签;
- 10 个运行加 2 个完成时,新请求只回收完成项;
- 跨会话回收前停止旧 daemon,旧 DOM 不残留;
- 12 个全运行时,第 13 个请求安全失败;
- 只有显式展示才允许浏览器前置;
- 隔离环境看不到日常 Chrome 的标签。
如果只测试“能打开网页”,权限泄漏、抢焦点、页面消失和错误回收都不会暴露。
八、从这次改造得到的五条产品原则
- 把执行成功与用户可用分开验收。
- 登录复用是能力,权限最小化是默认策略。
- 后台任务要把打扰成本计入成功标准。
- 完成结果应该可检查,清理是另一个显式动作。
- 容量不足时优先安全失败,不破坏运行中的工作。
Browser Use Cloud 适合并发、服务器、代理和反风控;独立本地 Chrome 适合固定账号、个人使用和长期复用登录。我的选择是后者,因为当前主要处理闲鱼、淘宝等需要人工登录的网站。
这套方案仍有维护成本:Browser Use 或 Browser Harness 升级后,本机补丁可能被覆盖,需要重跑冷启动、标签数量和焦点测试。
浏览器 Agent 从 Demo 走向日常工具,通常要先补齐边界、状态和失败方式,然后再谈更多动作。
九、发布也是产品交付:后台成功不等于读者可用
技术方案通过以后,我把完整过程整理成文章,并同步到知乎、人人都是产品经理、简书、小红书、抖音和哔哩哔哩。发布阶段出现的问题,和前面的浏览器改造几乎同构。
作者后台显示正文,不代表公开页没有丢图;内容包里存在 MD 文件,不代表读者能访问;评论已经提交,不代表未登录用户能看到;平台显示“提交成功”,也可能只是进入审核。
因此发布流程也需要一套外部验收:
- 用公开链接而不是编辑器地址检查文章;
- 确认正文、图片、代码块和提示词末句都存在;
- 分段评论必须编号连续;
- 审核中、已发布、待补充要分开记录;
- 长提示词不能只引用本地文件,也不能只依赖评论区。
本次 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 远程调试安全规则
本文由 @弗洛伊德 原创发布于人人都是产品经理。未经作者许可,禁止转载
题图来自作者提供

起点课堂会员权益





把完成页面保留下来让用户检查,这个设计太重要了。很多自动化工具只管“做完”不管“可查”,用户根本不知道Agent到底干了什么。保留现场就是保留信任。