跳转至

浏览器

浏览器客户端

apps/auth-api 提供 /app 服务。它的懒路由在浏览器启动后挂载交互式客户端。 src/renderer/src/features/web-client 拥有浏览器组合及其类型化的 WebWorkspaceRuntime 接口。它挂载现有的账户登录、页面栏、服务器栏、侧边栏和完整对话视图。ConversationRuntime 通过浏览器连接路由主机操作;其桌面默认值是 preload API。桌面端的 WorkspaceShell 和 Web 客户端在 LayoutProvider 下绘制同一个 WorkspaceFrame,因此两者的边栏几何结构、侧边栏调整器与紧凑模式、兼容性屏幕和用量报告槽位都是相同的。两个客户端都会弹出的覆盖层——加入、市场、共享智能体、服务器设置、全局搜索和频道创建——都是 WorkspaceOverlayViews.tsx 中由 props 驱动的视图。WorkspaceOverlays 从桌面上下文填充这些视图,Web 客户端则从主机连接填充它们,因此覆盖层根据其服务器所做的决定都集中在一处实现。Web 客户端为 PlatformProvider 提供了一个固定的 appInfo,代替主进程的应答。没有单独的 Web 仪表盘。

小屏幕在相同的对话和工作区组件之间切换。共享浏览器面板接收 Web 实时视图运行时,并隐藏不支持的本地控件。BrowserLiveView 接受显式运行时;桌面端和现有预览仍然默认使用与 preload 兼容的 API。Web Storybook 运行时通过 preview/mock-web-runtime.ts 使用 preview/mock-openbot.ts。

浏览器运行时使用 packages/team-client 进行目录、经过认证的 WebRTC 对等连接、Signal 恢复、文件传输和浏览器视图流。流编解码器位于 contracts 中;旧的主进程导入重新导出该编解码器,而不改变其线上格式。账户请求使用封闭的 /api/browser/* 操作列表。它们不能携带聊天请求。浏览器票证和会话终止要求与创建远程会话时相同的账户会话哈希。现有的 bearer-token 端点保持其行为。浏览器通过 v1/me/profile、v1/me/avatar 和 v1/me/sessions 编辑账户的名称、头像和会话,这些接口与 bearer 路由调用相同的 AuthService 方法和头像存储(avatar-storage.ts)。头像上传是唯一非 JSON 的写入;origin 和 X-OpenBot-Browser 检查仍然适用。web-account.ts 发起这些调用,Web 设置对话框的“Profile”标签页(WebAccountSettings.tsx)会显示它们。

所有者或管理员可获得桌面远程管理端的主机控制权。共享组件保持其桌面端口作为默认值,并接受注入的调用:web-server-settings.ts 通过 @openbot/team-client/team-admin-requests 为 ServerSettingsModal 提供主机身份、MCP 和存储路由,并通过封闭的 /api/browser/* 列表提供成员和邀请调用。Worker 应用与 bearer 路由相同的 RemoteControlPlane 角色检查。成员或角色变更会撤销主机上的所有会话;当目录仍列出该主机时,浏览器会重新连接一次。ConversationRuntime.admin 将技能和共享表调用传递给智能体设置面板,该面板在浏览器中只显示“Skills”和“Tables”。记忆、例程和文件保留在桌面端。Permissions 选择器通过 agent-admin 路由写入访问权限和自动批准。web-marketplace.ts 为 MarketplaceModal 提供调用:提供 /app 服务的账户服务的公共目录路由(@openbot/team-client/marketplace-catalog),以及通过 skills-admin-v1、agent-install-v1、agent-update-v1 和 mcp-servers-v1 在主机上安装。Try skill 和插件提示会在智能体的草稿中添加一行,和桌面端一样。共享智能体页面还链接到 /app?agent=<id>:WebApp 读取该 id 一次,移除查询参数,并在登录期间保留它;随后 AgentTemplateInstall 显示预览,主机通过 agent-install-v1 添加该智能体。浏览器不向市场提交任何内容。它可以通过 agent-publish-v1 发布主机智能体的共享链接,位于 ConversationRuntime.admin.agentTemplates 中:主机构建模板、检查是否包含秘密,并使用在主机上已登录的账户发布;浏览器只根据预览绘制共享卡片。主机从列表中添加的智能体在主机提供 agent-update-v1 时会获得“更新”;主机下载当前版本。web-provider-admin.ts 通过 providers-v1 路由应答桌面端的 providerAdmin 组,因此 ServerSettingsModal 的“Providers”部分使用与桌面应用相同的运行时、密钥、自定义提供程序和代码登录逻辑(provider-connection.ts、ProviderSettingsSection.tsx)。在桌面端,该部分仅显示活动服务器的提供程序,因为提供程序状态只存在于该服务器;对于另一台服务器,它会提供切换。浏览器应用主机的 status 事件,并在等待代码登录期间每 3 秒读取一次状态。提供程序密钥一直保留在对话框输入中,直到它被发送到主机。

浏览器登录、账户读取和连接票证始终可用。无需主机或 D1 迁移。有关七个审查范围、本地命令和发布检查,请参阅 Web 客户端交付。

浏览器聊天页面、草稿、文件字节和聊天可见性偏好保留在内存中。受保护的 cookie 保存账户凭据。本地存储保存账户范围的受信任主机公钥、共享文件面板的宽度,以及每个账户和主机的固定项目 id、折叠部分 id 和选定频道 id,而不包含聊天内容。频道 UI 采用 ChannelsPort 运行时;其桌面默认值是 preload API。Web Lock 允许每个账户和主机一个活动标签页,因为现有控制平面重用了该凭据的逻辑主机会话。切换主机会丢弃之前主机的聊天状态。临时连接丢失会保留草稿;发送失败的条目会保留在聊天中,并且不会自行重新发送。BroadcastChannel、焦点时的账户检查以及带签名的会话失效会在会话结束时清除访问权限。

MP3 和 MOV 附件使用现有的文件附件契约,不提供内联预览。导入操作在共享附件限制之内复制并哈希原始字节;它不会运行媒体编解码器,也不会提取帧或转录文本。这些格式的 MIME 类型来自文件扩展名,因此提供的图片或文本 MIME 类型无法启用预览。远程支持通过 media-attachments 能力以附加方式提供;已发布的协议适配器保持其现有含义。

ZIP 附件使用相同的契约,MIME 类型为 application/zip,不提供内联预览。导入操作会复制并哈希归档文件;OpenBot 永远不会解压它。远程支持通过 zip-attachments 能力以附加方式提供。

MP4 附件使用与 MP3 和 MOV 相同的契约,MIME 类型为 video/mp4。远程支持通过 mp4-attachments 能力以附加方式提供,因为仅具有 media-attachments 能力的主机会拒绝 MP4。

浏览器工具执行

browser-tools.ts 定义提供方模式,并将每次调用解析为类型化工具及其参数。browser-tool-actions.ts 将输入工具映射到 CDP 操作。它不拥有标签页,也不导入宿主。BrowserHost 负责标签页访问检查、操作队列、焦点、截止时间以及持久化浏览器状态。

浏览器会话和 service-worker 回退会移除 OpenBot/ 和 Electron/ user-agent 令牌。它们保留已安装的 Chromium 版本和宿主平台。对 accounts.google.com 的请求保留 Electron/ 令牌,因为如果没有该令牌,Google 会拒绝账户标识步骤。此例外不会改变页面身份或发送到其他站点的身份。

Electron 提供原生的 navigator.userAgentData,但不提供 client-hint 请求头。在 HTTPS 和回环 HTTP 上,宿主提供基本的 Chromium 品牌/版本、桌面标志和平台提示。它保留 Chromium 提供的提示,不添加高熵提示。重点检查命令是 bun run test:browser --scenario=identity;添加 --google-live 可执行 Google 标识符检查。本地身份报告位于 .openbot-build/browser-identity.json。

网站弹窗通过 Electron 的窗口创建钩子被纳入受管理的 WebContentsView 标签页。原生访客保留其 opener、请求体以及 opener 的浏览器会话。在该关系存续期间,本地标签页和代理工具结果会暴露 openerTabId。独立的 noopener 标签页在父级关闭后继续存在;依赖型弹窗随父级一起关闭。关闭弹窗会返回到其 opener。保存的弹窗 URL 会省略 OAuth 回调凭据。应用重启后,弹窗状态不会作为实时的 JavaScript 关系恢复。

原生 opener 组中的关联页面可以保留彼此文档的引用,包括接收过密钥的文档。浏览器会阻止跨站点访问,因此仅当另一个活动关联标签页中没有帧包含该密钥所属站点时,关联标签页中才提供安全输入卡片。宿主在卡片打开时和填充前各检查一次;否则该标签页需要人工接管。站点检查使用最后两个主机标签,这可以拒绝同一公共后缀下的两个站点,但无法放行其中一个站点。独立标签页仍有资格进行安全输入。不输入密钥的账户选择仍保持自动化。

代理在登录操作后使用 list_tabs,并在继续前检查新标签页。安全输入和接管仍处理密码、验证码、CAPTCHA 和通行密钥。被阻止的请求会产生原因说明,但不包含认证 URL 或请求数据。

代理指令在登录期间保持视口稳定,并要求在页面更改或被遮挡目标错误后使用新目标。X 的 Google 登录在 cookie 同意后从着陆页开始。X 可能为已移除的登录对话框保留 Google 回调,并报告 Input2SSO: Unsupported provider。对于当前尝试中出现的该错误,代理可以重新加载已注销的着陆页并重试一次,然后验证已认证的导航。此恢复过程不会在安全交接期间运行,也不会丢弃非登录工作。宿主不会重写站点脚本或削弱跨源安全策略。输入分发在这些检查和队列内运行。上传暂存阶段也会在检查本地文件访问之前使用共享解析器。

填充已检查的普通字段

openbot_browser.fill_fields 是原始浏览器目录中的延迟工具。它基于一个全新的快照修订版,替换主框架中的 1 到 16 个原生文本输入框或文本域。合并后的字面文本有 16 KiB UTF-8 限制。整个操作(包括最终捕获)的截止时间最多为 30 秒。它不执行提交、Enter、点击、导航、目标搜索或调用方脚本。现有的各个控件保持其行为。其控件事件使用已发布的 type 操作。

引擎在一个 CDP 租约内保留每个字段的精确原生句柄。预检会在任何输入之前解析并检查完整集合。它拒绝重复或不可用的引用、隐藏/禁用/只读字段、contenteditable、框架、密码输入框、一次性代码自动填充以及支付卡自动填充。它接受原生输入类型 text、search、tel、url 和 email,以及 textarea。HTML 元数据不能完全检测密钥或表单副作用。代理必须首先检查普通表单。认证、支付和不确定的用户决策使用现有的安全输入或接管流程。

每次填充都会重新检查相同的原生句柄、快照指纹、原始字段值、可编辑元数据、主框架文档和来源。它不会为已更改的目标寻找替代。原生导航代次和文档节点身份可检测同 URL 的重新加载。焦点处理器在可信输入之前会接受第二次检查。宿主在每个字段之前以及在释放最终捕获之前,会重新检查标签页所有权、密钥保护和接管。CDP 模拟页面焦点;该操作不请求原生窗口或应用程序焦点。

一个标签页队列槽位拥有所有字段、一次最终页面稳定等待和一次有界的最终语义快照。成功输入由其原生字段值确认。结果包含从零开始的 confirmed 索引、failedIndex 和 uncertainIndex,进度记录中不包含字段文本或页面异常。partial 表示较早的字段可能保持已更改状态。未确认完成就发送的输入是不确定的。inputCompleted: true 且 status: snapshot_failed 表示输入已完成但捕获失败。调用方必须读取页面后才能决定如何继续。不承诺自动重试、撤销或原子事务。

取消操作会阻止尚未分派的字段。已分派的输入及其确认会在队列槽位结束前排空。响应截止时间可以更早返回,但后续的标签页操作仍会等待该被占有的工作。宿主无法取消的外部调试器必须排空其命令。引擎在完成时释放保留的句柄及其租约。已知失败使用固定的原因代码;页面异常不会进入新的进度结果或诊断操作。

bun scripts/browser-fill-fields-e2e.ts 只编译其原生 fixture,并使用一个私有的隐藏 Electron 进程,配合临时配置文件和合成的回环表单。要获得通过的结果,它不得显示窗口或请求原生焦点。它是本地检查,而非 CI 步骤;在 Linux 上,请在 xvfb-run -a 下运行。报告位于 .openbot-build/browser-fill-fields-e2e/report.json。报告包含实际运行时版本、已完成的用例、三对预热配对和二十对测量配对、序列化结果字节数以及观察到的快照计数。测量包括工具执行和回环 fixture 确认。它们不测量提供方 token、模型速度或计费成本。

浏览器会话

每个 agent 都有一个浏览器配置文件:其新标签页打开时所处的持久分区。agent 的 ownBrowserSession 和 browserProfileId(agent_json)字段用于选择该配置文件:

选项 分区
共享(默认) persist:openbot-browser
仅此 agent persist:openbot-browser-agent-<key>。该键是 agent ID 的 SHA-256 的前 32 个十六进制字符。
命名配置文件 persist:openbot-browser-profile-<id>。该 ID 是 32 个随机十六进制字符。

命名配置文件是 browser_profiles 表(迁移 45)中的行。多个 agent 可以使用一个配置文件,因此它们共享其中的登录状态;配置文件没有所属 agent,下面的标签页规则也不会改变。名称只是一个标签,永远不会成为路径或分区,因此重命名会保留数据。带有 browserProfileId 的 agent 也带有 ownBrowserSession: true。因此,在降级后,旧版本构建会将该 agent 打开在它自己的会话中,而绝不会在共享会话中打开。重复的 agent 和导入的 agent 从 Shared 开始。用户在 agent 面板中的标签页使用该 agent 的分区,因此用户可以在此为该 agent 登录。

openPrivate 在内存分区中为另一个人打开登录页面。该标签页不会被保存,并且当其最后一个标签页关闭时,其存储会被清除。

标签页在其整个生命周期内保持其分区,其弹出窗口也使用相同的分区。状态文件将 agent 键保存为 agentSession,将命名配置文件保存为 profile,同时附带一个由配置文件 ID 生成的 agentSession 键,旧版本构建会读取该键。重启后,每个标签页都会在其保存的分区中被恢复,无论当前设置如何。没有键的标签页(例如来自旧版本的标签页)会在共享分区中打开。带有格式错误的键或配置文件 ID,或带有已删除配置文件的 ID 的标签页会被丢弃,因此它绝不会在登录到另一个账户的状态下打开。为某个配置文件已不存在的 agent 新建标签页时,会以 browserProfileMissing 失败;它绝不会回退到 Shared。#configureSession 会将相同的用户代理、请求头、权限和下载策略应用于每个分区。关闭和 flushPersistentStorage 会刷新每个持久分区。

src/main/browser-profile-service.ts 拥有这些操作:列出、创建(名称 1 到 60 个字符,唯一,最多 100 个配置文件)、更新(名称或颜色)、清除数据、删除和分配。清除和删除会先关闭分区中的标签页,再清除其存储和缓存。删除还会将配置文件的 agent 移动到 Shared。每个命名配置文件都有一个颜色(hue),即 agent 头像色调之一。创建会分配当前被最少配置文件使用的颜色(nextBrowserProfileHue);服务器设置 > 浏览器配置文件 可以更改它。浏览器视图会在工具栏上显示带颜色的配置文件标签,在标签页下方显示一条该颜色的线,并在共享配置文件之外的标签页的标签页悬停提示中显示名称(agent:admin:list-browser-tab-profiles)。选择器用灰点标记 Shared,这是没有任何命名配置文件使用的颜色。“仅此 agent”没有颜色,且选择器只向已经在使用它的 agent 提供该选项,以便该 agent 保留其登录状态。已加入服务器的所有者或管理员可以通过 browser-profiles-v1(route-browser-profiles.ts)使用相同的操作;每个成员都能读取他们可以看见的标签页的标签,而私有标签页对成员隐藏。没有该能力的宿主会以 agentSettingsUnsupported 应答,客户端不会为其显示选择器、区块或标签。

标签页生命周期

标签页何时关闭由 agent 决定。回合结束时,没有任何机制会关闭标签页:close_tab 是唯一的清理路径,提示会要求 agent 在任务不再需要该标签页时使用它。因此,标签页按设计会存在于其回合之后,这让同一线程中的下一个回合能够在上一个回合离开的页面中继续,也让用户之后可以阅读结果。

不存在用户拥有的标签页。标签页带有 ownerThreadId 和 ownerAgentId,agent 可以读取并关闭其自身线程中的任何标签页,包括用户在该线程中打开的标签页。唯一的硬性限制是接管:当用户持有标签页时,任何 agent 工具都不能触碰它。这一点在 BrowserHost.#requireToolTab 中强制执行,这是每个携带标签页的工具都会经过的最低层,因此它同样适用于从未到达 AgentService 的调用方——即视图网关和远程宿主。AgentService 中基于 agent 的拒绝逻辑保留在其旁边,而不是被合并进去:它还覆盖了 open 和 list_tabs,这两者不指明具体标签页,并且它以拒绝而非错误作为应答。

事件 标签页
回合成功 保持打开,除非 agent 调用了 close_tab。
已取消、已中断或已失败 保持打开。完成时仅清除控制会话。
重试 同一线程和 agent,因此相同的标签页仍然可访问。
重启 从浏览器自身的状态文件恢复。
空闲 30 分钟(内存不足时为 5 分钟) 保持打开,但其页面会被卸载。下次使用时重新加载页面。
代理被删除 该代理的标签页被关闭,包括仅持有其线程 ID 的遗留标签页。其自身的浏览器会话被清除。
浏览器配置文件变更 该代理的标签页被关闭。其下一个标签页在其所选配置文件中打开。两个配置文件都保留各自的数据。
配置文件被清除或删除 该配置文件中的每个标签页都被关闭,然后其数据被清除。
接管保持中 没有任何代理工具接触该标签页,close_tab 也不例外。

删除代理是一次全面的清理,它之所以存在,是因为这些标签页否则将无法访问:没有代理能通过针对它们的所有者检查,而渲染器按代理列出标签页,因此在多次重启后,它们会持有一个用户无法看到也无法关闭的视图。关闭操作是幂等的——close() 在不持有的 ID 上会提前返回——并且标签页 ID 是 UUID,任何层都没有重新排序功能,因此过期的 ID 永远不会指向取代它的标签页。

远程服务器上的成员无法看到主机的标签页,因为该标签页是主机自己屏幕上的原生视图。browser-view-gateway.ts 通过会话和 WebSocket 解决这个问题:BrowserHost 通过 CDP 流式传输标签页,网关将每一帧作为字节发送,并在最后一帧时长的几分之一内,将返回的指针和键盘输入通过相同的访问检查进行分发。帧不进入每个标签页的操作队列,因此观看永远不会延迟工具调用。browser-view-client.ts 是客户端部分,它复用远程桌面 WebSocket 隧道,而不是新增 WebRTC 通道。browser-view 能力用于说明主机是否同时具备两者。

移动应用(iOS 和 Android)自行绘制指针,因此需要页面的光标。具有 browser-view-cursor 能力的主机会从标签页的 cursor-changed 事件发送 cursor 文本消息,发送到以 cursor=1 打开的套接字;桌面端和 Web 客户端不会请求该消息。手机使用桌面端和 Web 客户端的 browser-view-clipboard 输入:paste、copy(以 copied 或 copyTooLarge 回应)和 cut,以及用于全选的 Cmd+A。只读取 copied 回应的客户端永远不会请求光标或菜单,因此它不会收到其他文本消息。具有 browser-view-context-menu 能力的主机会将右键点击的菜单以 context-menu 消息发送给发起该菜单的套接字(以 menu=1 打开),并且不打开原生菜单:该菜单会显示在主机屏幕上。只有 http 和 https 地址会包含在消息中。具有 browser-view-viewport 能力的主机会在该套接字打开期间,通过与代理的 set_environment 相同的设备指标覆盖,将页面保持在该套接字以 viewport=WxH 请求的尺寸,因此主机窗口的调整大小不会改变页面。通过 set_environment 设置了自定义尺寸的标签页会保持该尺寸。手机会请求 1280x800 的尺寸。在手机上,视图运行在持有 Team 对等方的隐藏页面中(features/browser/model/browser-view-bridge.ts)。该页面将每一帧以 base64 形式发送到屏幕,并保留最新帧,直到屏幕绘制完前一帧。后退、前进、刷新、新建标签页和关闭标签页使用已发布的 browser-control 路由。账户服务器的 browser 移动功能开关会针对某个应用版本关闭手机浏览器(apps/auth-api/src/server/mobile-features-config.ts)。

安全浏览器认证

openbot_browser.submit_secret 使用现有的注意力/接管生命周期,并带有可选的公开秘密请求元数据。注意力注册表会创建全新的请求 ID,并持有挂起的回应。秘密通过专用的类型化 IPC 端点或可选的 browser-secret-handoff Team API 能力传输,绝不会作为提示回答或提供方工具参数。冻结的协议投影会继续向旧版客户端显示普通的接管。当前编解码器在这些投影之外携带经过验证的元数据。没有数据库模式变更。

密码也可以进入运行代理的服务器的 Vault。本地应用通过 IPC 发送 saveToVault。远程客户端在 browser-secret-vault-v1 上发送相同的提交,该端点只有所有者或管理员可以使用;已发布的路由保留其字段。卡片会在每次密码请求时显示保存框,对于成员或较旧的主机,它会显示关闭的保存框并附上原因。

浏览器主机拥有保护状态,并将提交操作排在现有浏览器工作之后依次执行。CDP 在同意之前解析字段,并在提交之前再次检查文档和来源。主机停止录制、抑制页面诊断、阻止检查与捕获、拒绝远程输入,并使现有的实时视图流失效。捕获保护在同文档导航或不确定的提交之后仍然保留。在完成的提交操作之后(且没有文档替换),主机会等待最多五秒,然后清空已填写的字段。

当每个字段都为空,且自动化世界中的扫描在标题、URL、文本节点、值或属性(包括开放的 shadow root)中都未找到该值时,它会保留文档,以便单页登录可以显示其下一步,并阻止开启方组中的评估和录制,直到主框架导航发生,该导航也会清除历史记录。否则,它会使用 GET 加载当前 URL 以替换文档,而不重新提交表单 POST。失败则保持保护状态并回退到接管。新文档会解除保护并清除导航历史;仅凭手动完成接管无法解除保护。秘密不会被重试。在不受支持的框架内进行身份验证、不明确的 OAuth 账户选择、CAPTCHA、通行密钥以及支付确认都会使用接管。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 openbo/openbot