OpenWork 浏览器任务机制深度解析:会话级标签、WebMCP 站点工具与四重权限边界
【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork
本文以 OpenWork 桌面端内置浏览器任务(Browser Tasks)机制为核心,剖析其如何在网站工具、DOM 控件与图像之间保持对话上下文、标签与登录状态的一致,并讲解导航同意、读取访问、操作批准与结果披露四类权限如何独立生效。读完本文,你将掌握browser_tabs/browser_open/browser_observe/browser_act/browser_navigate/browser_handoff六类工具的执行契约、WebMCP 兼容层的 Schema 安全子集,以及execution.browserOrigins与blockBrowserUploads等托管策略的精确匹配语义,可用于理解或扩展同类安全浏览器代理的设计。
一、核心模型:会话、标签与持久登录上下文
浏览器任务的第一个设计决策是:每个任务(conversation)拥有自己的标签集合,且所有标签共享同一个持久化浏览器分区。文档 task-experience.md 明确指出,一个浏览器任务会在网站工具、DOM 控件和图像之间持续保留其对话、选中标签与已登录上下文;内置浏览器使用 Electron 的持久浏览器分区(persistent browser partition)。标签所有权(tab ownership)隔离的是控制权与可见性,它并不会为每个对话创建不同的网站账户——这正是它与"账户隔离"在语义上的根本区别。
这一设计在源码中有直接印证。桌面端执行器 browser-task.mjs 在文件头声明了三个执行常量:
const MAX_OPERATION_MS = 30_000; // 单次操作超时,含首次打开等待批准 const OBSERVATION_MS = 15_000; // DOM 观察快照的有效期 const TRUST = "untrusted-site-content"; // 页面内容一律视为不受信任也就是说:一次打开操作的等待窗口是 30 秒,一次 DOM 观察最多 15 秒有效。这两个数值会贯穿本文后续的"用户体验流程"与"执行契约"两个章节。
标签所有权与共享登录的边界
从browser-task.mjs的错误码可以看到所有权检查的执行位置:
wrong_conversation:"That tab belongs to another conversation"——其他会话无法检查该标签或复用其工具句柄;paused:用户接管浏览器后,会话的所有浏览器操作被暂停;needs_attention:需要批准但目标标签不可见(在后台),返回该码让用户切到标签后重试;sign_in_required:页面含密码或一次性代码字段,必须由真人接管。
对应关系可以在 openwork-chrome-devtools.ts 的服务端工具定义中看到:六个工具全部通过uiBridgeRequest("/browser/task", ...)走桌面端 UI 桥,并且请求上下文强制要求sessionID,缺少请求会话时返回missing_session。这说明会话 ID 是可信执行上下文提供的,而不是模型参数——模型无法伪造它来访问别的会话的标签。
二、用户体验流程:六步标准化操作路径
文档描述了一个稳定的六步用户体验,评测测试 webmcp-browser-agent.e2e.test.ts 将其完整固化为断言:
第 1 步:打开并确认导航。任务先列出本会话的标签(browser_tabs),若存在完全匹配的页面则复用,否则在新建的属主标签中打开请求的 URL(browser_open)。加载一个新 origin 之前,浏览器面板会出现Allow website navigation?审查面板;用户点击Allow origin in this tab表示授权该标签、该会话下精确的 scheme、host 与 port——注意它不授予读取,也不授予任何操作。被批准之前,新标签保持空白(测试中断言label: "New tab"且pageRequests为空)。测试还特别验证了:localhost 预览同样需要显式批准,不存在"本地豁免"或"私有网络一律禁止"的兜底规则——测试中http://localhost:${port}需要单独的Allow origin in this tab才能放行。此外,测试要求"模糊引用必须使用真实标签上下文":工具总是携带具体的tabId,绝不猜测"这个标签"指哪个。
第 2 步:批准读取访问。打开成功后,面板出现Allow reading this origin,它授权的是"在当前桌面进程内、本会话对该 origin 的读取"。测试断言在点击该按钮之前不会发生任何 GET 请求、不会出现站点工具列表。
第 3 步:发现站点工具。站点工具通过webmcp_list_tools发现。宿主优先使用结构化的集成或站点工具;若没有,则退回到 DOM 观察与页面可见控件(browser_observe)。当控件没有可用的 DOM 引用时(例如 canvas、iframe 内部),一张新的页面图像可以支撑坐标式操作(browser_act的x/y点击)。
第 4 步:逐操作批准。每个网站动作都要单独审查(Allow once)。网站的只读注解(readOnlyHint)只是建议性的,不构成信任声明。批准会把操作绑定到它的标签和当前页面。站点回调返回后,结果先留在本地,用户在浏览器面板审查完整的有界结果后再点Share result——这一步单独授权向对话及其模型提供商披露结果。拒绝披露(Deny)保持 payload 私有,但它不会撤销已发生的动作,也不允许自动重试。评测测试 webmcp-browser-agent.e2e.test.ts 用受控 fixture 验证了这一点:在点 "Allow once" 之前 fixture 记录数为 0;点后出现一条已登录的 webmcp 调用,但模型与转录中都还没有结果;点 "Share result" 之后引擎才收到结果,并再通过一次browser_observe观察到页面上的 "Saved 1" 后才给出最终答案。
第 5 步:接管(Take over)与恢复。当页面需要登录、验证码等真人操作时,用户选择Take over暂停会话的浏览器操作,直接在页面内完成登录,再点Resume browser。恢复后的下一个动作必须重新观察页面。测试 webmcp-browser-agent.e2e.test.ts 验证了:接管期间observe/open全部返回paused,真人通过表单提交建立 fixture 会话后,同一属主标签恢复并显示 "Session active"。打开另一个标签无法绕过接管——paused是按会话级别生效的。
第 6 步:观察结果。任务必须通过新的观察确认用户期望的结果。输入事件被分发(dispatched: true)和站点回调返回,都与"用户想要的结果已达成"是两回事。动作返回的是派发回执(outcome: "not_yet_verified"),引擎必须再观察一次页面才能宣告完成。
后台标签与弹窗语义
- 后台标签保留其会话归属,且不能切换可见会话。测试 webmcp-browser-agent.e2e.test.ts 验证了:后台打开会分配一个审查标签但保持原可见会话不变,此时对原标签的
act/site_tool全部返回needs_attention且dispatched: false,用户切回该会话的标签后才出现批准按钮。 - 首次打开可以停在空白标签上等待面板挂载与属主选择,但必须在任务 30 秒超时内;超时或取消会驳回待处理批准并释放新标签,浏览器面板对话框更长的 60 秒限制也无法"复活"它。
- 普通弹窗窗口停留在属主内置标签中,复用相同的 profile 与 opener;非 HTTP(S) 的弹窗被拒绝。
- 被关闭的标签是一个显式错误(
tab_closed),从不静默替换。
三、执行契约:工具面、观察生命周期与禁止项
工具面
浏览器任务暴露六个工具(openwork-chrome-devtools.ts):
| 工具 | 作用 | 关键参数 |
|---|---|---|
browser_tabs | 列出本会话的内置浏览器标签(不含其他会话与外部浏览器 profile) | 无 |
browser_open | 在本会话打开网站,复用完全匹配 URL 的既有标签;不读取页面、不授权动作 | url(完整 URL)、tabId(可选)、provider: "builtin" | "auto" |
browser_observe | 读取当前页面与可见控件,返回新的observationId与短生命周期元素引用;可选返回图像 | tabId、includeImage |
browser_act | 针对一次新鲜观察派发一个动作;每个动作都需要单独用户批准;返回派发回执而非任务成功 | tabId、observationId、action(click/fill/key/scroll) |
browser_navigate | 导航本会话选中标签;组织策略适用于导航与重定向;导航后需重新观察与发现站点工具 | tabId、url |
browser_handoff | 暂停浏览器操作以便用户直接登录或完成步骤;绝不在聊天中索要凭据;只有用户能在面板恢复 | tabId |
站点工具保持webmcp_list_tools与webmcp_call_tool两个入口(见 openwork-extensions-preview.ts 的参数定义:tabId可省略表示活动标签,toolId是最近一次webmcp_list_tools返回的不透明句柄)。认证的 loopback 桥是内部桌面能力,不是外部浏览器连接;browser_open的参数枚举里只有builtin与auto,unsupported_browser错误明确写着"External browser control is not connected"。
禁止暴露的能力
支持的工具不暴露:任意代码求值、原始 CDP、cookies、存储、网络响应体、上传或系统剪贴板。注意这是浏览器工具边界——它不会为无关的 shell 工具或用户自行添加、拥有独立机器权限的插件提供沙箱。执行器文件头也写明:"No model-specific API, arbitrary script execution, cookies or raw CDP surface."
观察生命周期与动作约束
- 每标签单操作:一个标签同一时刻只允许一个操作,不存在变更队列。
busy错误要求"等待其结果,不要排队另一个动作"。 - 观察快照过期:DOM 观察携带随机 ID,在 15 秒后、DOM 变更(页面内嵌了 MutationObserver)、导航、滚动或视口变化时全部失效(browser-task.mjs 同时校验 ID、
Date.now() - observed.at > OBSERVATION_MS、webMCP 修订号与当前 URL)。 - 坐标点击:基于图像的坐标点击要求图像按页面视口缩放,并在派发前重新检查像素;
prepareAction中还会拒绝指向input[type="password"]、input[type="file"]、input[autocomplete="one-time-code"]的命中(返回sign_in_required)。 - 动作消费观察:动作在派发前消耗其观察快照;失败或取消会清除它。超时与不确定结果禁止自动重放,包括切换到另一种方法去重复该动作——这正是
outcome: "not_yet_verified"与result_withheld语义存在的意义。
四、权限边界:四类独立批准与托管策略
这是整个机制安全性的核心。文档强调:导航同意、浏览器读取访问、操作批准与结果披露是互相独立的四件事,任何一环都不能推导出另一环。
导航授权的生命周期
导航授权是内存态、按标签与属主作用域生效,并在接管、取消或关闭时全部撤销;它永远不会被新标签或弹窗继承。接管会中止待处理的任务加载;只有显式的地址栏输入、后退、前进或刷新操作才能启用"无任务授权"的手动导航——网页内的鼠标和键盘输入不会产生导航授权(评测中专门用 CDP 注入的点击与键盘事件尝试触发重定向,结果目标请求数为零,webmcp-browser-agent.e2e.test.ts)。恢复(Resume)之后需要新的导航同意。
请求级拦截:唯一的 onBeforeRequest 监听器
Electron 现有的installPolicyRequestHook是唯一的onBeforeRequest监听器,覆盖所有网络请求:重定向、frame、子资源、脚本化请求与上传。blockBrowserUploads是权威性的——用户同意不能绕过它,被拒绝的请求也从不回退到外部浏览器。
同一个监听器还会在派发前"扣住"任务控制的主框架请求(包括跨源重定向),直到目标 origin 被批准;批准后会重新检查托管策略,并在放行前检查取消、标签身份与归属。暂停的任务既不能获取新授权,也不能把迟到的重定向当作手动浏览。遗留的自动化打开(legacy automation open)同样要经过任务宿主的 pre-load 同意门。
边界声明:这不是完整的出口沙箱
origin 同意门只作用于主框架导航,不作用于每个子资源:frame、图片、脚本、fetch 等仍由既有的托管请求策略管辖。如果该策略允许,一个已加载的网站可以自行联系其他 origin 而不再弹出导航提示。此外,精确的 URL origin 匹配不是DNS/IP 分类,也不是DNS-rebinding 防御;内置浏览器仍然跨会话共享其持久登录 profile。这些都是设计上有意保留的边界。
托管策略的精确语义
execution.browserOrigins与blockBrowserUploads的 schema 定义在 desktop-policies.ts:
browserOrigins:最多 100 个条目;每个值必须是http/https 且无路径、无凭据、无查询、无 hash的 URL,并会被new URL(value).origin规范化——因此它匹配的是精确的 scheme、host 与 port;- 多策略求值采用交集(intersect):
resolveDesktopExecutionPolicy在多个团队策略都存在browserOrigins时取它们的交集(desktop-policies.ts),绝不 union 主机模式或通配符; blockBrowserUploads:布尔值,多个策略中任一为true即生效(||=)。
服务端策略裁决在 managed-policy-rules.ts 中实现:browserOrigins存在时,非精确 origin 的browser/webfetch一律拒绝("This website is not approved by your organization."),并且webfetch/websearch也会被整体改写为"请使用内置浏览器打开已批准的网站"(managed-policy-rules.ts);blockBrowserUploads开启时,wss://请求、携带上传的动作或非 GET/HEAD/OPTIONS 方法都会被拒绝。executionPolicyTargets(managed-policy-rules.ts)则把这些执行策略映射到引擎权限规则:browserOrigins -> ["engine.webfetch", "browser.request"],blockBrowserUploads -> ["browser.request"]。
这些批准永远不能扩大组织的托管策略——现有的异步checkPolicy边界检查任务访问、DOM 动作、站点工具发现与调用以及结果共享,不存在渲染器管理的网站授权或并行的策略缓存。
五、WebMCP 兼容层:命令式 API 与有界 Schema
命令式document.modelContext路径
宿主实现的是命令式(imperative)的document.modelContext路径,通过隔离的 preload 桥接入。当运行时本身不提供该 API 时,兼容实现会补上它——这一逻辑位于 browser-content-preload.cjs:脚本被序列化注入网站的独立 JavaScript 世界,先检查document.modelContext是否存在;不存在时才安装兼容实现,且只安装一个隔离、沙箱化的桥,绝不向网站 JavaScript 暴露 Node API(require、process、Buffer全部为undefined,测试对此有逐项断言)。兼容实现还会通过 Permissions Policy 桥(__openworkWebMcpPolicyV1)校验文档是 origin-keyed 且被允许。
发现与执行的每次校验
宿主侧 broker 位于 webmcp-host.mjs。发现阶段会验证:工具名(^[A-Za-z0-9_.-]{1,128}$)、有界 JSON Schema、frame 策略与 origin;每次调用在批准前和批准后都会重新检查文档、frame、schema 与注册状态。句柄(handle)携带请求会话与导航修订号,跨越导航的发现会被拒绝而不是发布过期句柄(stale_tool)。
Schema 子集与 rejectedTools
输入 schema 使用有界 JSON Schema 2020-12 子集(webmcp-host.mjs):
pattern、patternProperties以及所有format验证器(包括regex)不支持——包括嵌套 schema 与definitions内部;主线程永远不会编译网站提供的正则;$ref只允许指向命名的本地$defs或definitions条目;任意 JSON 指针、远程引用、$dynamicRef均不支持;- 复杂度有硬性上限:schema 最大 64KB、嵌套深度 32、节点数 5000(webmcp-host.mjs),另有每 tab 128 个工具、每 frame 32 个工具、每 tab 同时最多 4 个执行等配额。
被拒绝的描述符会在rejectedTools中逐个报告,而不会隐藏仍有效的工具;这类 schema 是在编译或操作批准之前就被拒绝的,绝不会"静默接受但忽略约束"。注意字面数据与属性名中仍可包含这些词,限制的是关键字本身。
支持范围与信任模型
支持同源 frame 与显式委托的安全跨源 frame;声明式 HTML 表单工具、旧的navigator.modelContextAPI 与外部浏览器 WebMCP 均不支持,返回的能力元数据会显式命名这些限制。网站结果始终是不受信任的(trust: "untrusted-site-content"),即使其工具注解声称只读——readOnlyHint仅作参考,不能作为跳过审查的依据。测试对 frame 委托有完整覆盖:允许的同源 frame 注册工具、被 Permissions Policy 拒绝的 frame 抛NotAllowedError、四个 frame 全部验证require/process/Buffer为undefined(webmcp-browser-agent.e2e.test.ts)。
结果披露与收据语义
站点回调可能在任意字段名下返回机密(例如 session token),因此按 key 名脱敏是不够的——回调结果保持本地,直到单独的披露审查成功。缺少审查支持、用户拒绝、取消或策略丢失时,宿主会扣留 payload 并保留"不确定操作"的收据(result_withheld,错误消息明确指示"不要重复该动作",webmcp-host.mjs)。测试验证了拒绝分享后result为undefined且序列化结果不包含 fixture 会话串(webmcp-browser-agent.e2e.test.ts)。
六、集成边界与既有实现复用
浏览器任务刻意复用既有实现,而不是重新发明轮子:
- 会话属主标签、后台视图停放、视口恢复、团队执行策略、原生计算机使用、工作流仪表板面板继续由各自当前实现拥有;
- 外部 profile 同步由合并的#4452单独提供;
- #4481 的浏览器访问机制已被合并的执行策略(#4564)取代;
- 此功能不新增Den 策略字段,也不提供登录导入 API。
原生边界整体遵循 Electron 的 sandbox contract:弹窗无论网站提供什么特性,都被强制应用与普通标签相同的沙箱、上下文隔离与同源安全;主进程强制导航控制、渲染器保持隔离、弹窗创建受控。browser-task.mjs中的页面观察代码运行在隔离世界(WORLD = 1001),拥有 DOM 访问但没有任何页面全局、Node、IPC、profile 或网络能力,引用也从不进入页面 DOM。原生计算机使用独立拥有 OS 应用与窗口会话,浏览器工具不会获取 OS 指针控制。
七、验证体系与已知限制
评测旅程(Journeys)
webmcp-browser-agent是浏览器任务的主评测:真实引擎插件 + 确定性 provider + 受控网站 witnesses,覆盖导航同意、登录态调用、DOM/图像回退、弹窗隔离、精确 iframe 委托、取消、过期观察与"观察到的完成"(webmcp-browser-agent.e2e.test.ts);其测试世界与策略设置工具在 browser-webmcp.ts,可动态下发browserOrigins与blockBrowserUploads并触发 Den 设置变更事件。browser-tabs-owned-by-thread与browser-panel-viewport-recovery负责后台可见性与视口恢复。- 托管策略覆盖必须通过既有 native/server 边界验证精确 origin、交集与上传限制。仅源码检查不是运行时证明——这些旅程在重构后需要重新产出新鲜证据。
明确的限制
- 实现与模型无关:纯文本模型可以使用页面文本与站点工具;视觉工作(图像观察、坐标操作)需要图像能力的模型或用户协助。
- 桌面与本地服务器必须在同一台机器上运行;没有自带桌面浏览器的远程服务器会返回不可用结果,且不会去连接另一台机器的外部浏览器。
- 确定性 provider 能验证工具可用性、执行上下文、结果交付与会话内的已验证完成答案,但不证明每个 provider 的开放式规划质量。
- 以下内容明确不在浏览器任务子集内:对外部浏览器 profile 的直接控制、声明式 WebMCP、封闭 shadow-root 的 DOM 引用、文件传输,以及重启后活动标签句柄的恢复;登录同步保留其单独记录的支持限制。
结语
OpenWork 的浏览器任务机制把"模型可控的浏览器"收敛为一套纪律严明的执行契约:会话级标签所有权保证并发安全,四重独立批准把导航、读取、操作与披露彻底解耦,document.modelContext兼容层用有界 Schema 把网站输入限制在可验证的安全子集内,而installPolicyRequestHook与execution.browserOrigins则把组织策略钉在请求流的唯一咽喉。对希望实现同类安全浏览器代理的工程师而言,这份设计的核心启示是:信任边界要落在宿主进程与请求拦截层,而非模型或页面代码——观察快照有时效、动作必须消费观察、结果必须单独披露、失败永不自动重试,这四个规则共同构成了可审计、可回放、可验证的浏览器任务体验。
【免费下载链接】openworkThe open-source alternative to Claude Cowork (powered by opencode)项目地址: https://gitcode.com/GitHub_Trending/ope/openwork
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考