DeepChat Agent 浏览器画中画:从后台渲染宿主到 NativeKit 原生 PiP 面板的实现解析
发布时间:2026/9/17 9:16:03 作者:尧图编辑部 阅读量:1,286

DeepChat Agent 浏览器画中画从后台渲染宿主到 NativeKit 原生 PiP 面板的实现解析【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat本文围绕 DeepChat 的 Agent 浏览器预览规格docs/features/agent-browser-pip/spec.md展开讲解当 Agent 会话在后台操作网页时DeepChat 如何用「单一实时页面 后台渲染宿主 原生只读 PiP 面板」三者协作让用户在不打开侧边栏的情况下看到 Agent 正在访问的页面。读完后你可以理解页面 reparent 的约束模型、捕获帧的序列化管线、原生覆盖层的进程级所有权仲裁以及面板交接panel handoff与 run 级关闭的完整生命周期规则并能对照源码定位到每个契约的落点。背景为什么需要一套独立的 Agent 浏览器呈现层DeepChat 内置了名为 YoBrowser 的嵌入式浏览器能力Agent 可以通过load_url、cdp_send等工具直接导航页面并执行 CDP 命令。当 Agent 在后台默默操作网页时用户在聊天窗口里是「盲操作」的看不到 Agent 正在加载哪个页面、滚动到哪、点击了哪里。PiPPicture-in-Picture预览正是为此设计的呈现层右侧面板保持关闭时符合条件的 Agent 会话可以弹出一个原生只读预览展示 Agent 页面的实时画面如果右侧已经打开了任何面板或原生能力不可用Agent 的浏览器动作会走既有的 Browser 侧边栏而不是强行弹出 PiP预览是只读图像不是第二个浏览器、更不是页面的另一个原生父级打开面板就是把同一个实时页面「搬」进面板。规格文档明确当前状态单页 Agent 浏览器预览与面板交接已实现可见多标签条、Fit desktop 和完整响应式/展开页面工作流被推迟到 docs/features/agent-browser-pip/plan.md 与 docs/features/agent-browser-pip/tasks.md 中继续跟踪原生呈现层的平台交互与性能验证仍是开放项。原生的窗口拖拽、工作区钳制与 z-order 由 NativeKit 定义见 docs/architecture/nativekit-agent-browser-pip/spec.md。此外Computer Use 快照 与 Browser 共享同一份「进程全局呈现所有权」但两者的功能契约并不合并——Browser 仍然独立拥有自己的页面、捕获循环、面板交接和 run 级关闭。组件职责图谁拥有什么规格中的「Current Ownership」一节把职责切得很干净源码逐条对得上组件路径职责YoBrowserPresentersrc/main/desktop/browser/YoBrowserPresenter.ts主进程侧唯一持有者每个聊天会话一份SessionBrowserState包含实时WebContentsView、页面封装、owneragent/user、Agent run 标识、放置信息、预览宿主、捕获状态YoBrowserToolHandlersrc/main/desktop/browser/YoBrowserToolHandler.ts接受来自 Agent 执行侧的 run 标识并转发给 presenter工具事件携带 owner/source 与 run 标识ChatSidePanel.vuesrc/renderer/src/components/sidepanel/ChatSidePanel.vue为「显式用户导航」或「面板已打开」打开 Browser单独的 Agent open 请求不会强行打开关闭中的面板BrowserPanel.vuesrc/renderer/src/components/sidepanel/BrowserPanel.vue提供稳定的可见 bounds通过类型化客户端管理页面附着AgentBrowserPiP.vuesrc/renderer/src/components/browser/AgentBrowserPiP.vue推导当前会话/工作 run/窗口/面板资格并对预览模式请求做合并coalescingAgentPreviewCoordinatorsrc/main/desktop/preview/AgentPreviewCoordinator.ts拥有跨 Browser 与 Computer Use 的「唯一可见原生呈现」NativeKit 拥有拖拽、显示工作区钳制与原生 z-order渲染进程只收到来源特定的动作与 surface 状态原生图像帧不经过渲染进程 IPCyoBrowserSession.tssrc/main/desktop/browser/yoBrowserSession.ts从捆绑运行时推导桌面 Chromium 用户代理process.versions.chrome窄页面 bounds 不会把浏览器切换为移动端/触摸语义关键设计点是当前公开状态是每会话一个页面而不是推迟中的多标签工作区模型。SessionBrowserState在 YoBrowserPresenter.ts 中定义字段包含previewHost后台BaseWindow、previewMode三种预览模式、previewSurfacenative-overlay/renderer-canvas/none、previewEpoch、previewClaimSequence等——后两者正是下文「epoch 失效」与「进程级 claim」机制的载体。实时页面与后台渲染宿主一个页面两种父级这是整个方案的骨架。面板关闭期间Agent 页面停在一个无焦点的后台渲染宿主BaseWindow中该宿主只为 Chromium 提供显示表面透明、位于屏幕外且不接收任何用户输入。后台页面的 viewport 固定为1280 × 800与预览图尺寸无关Background render host -- Agent WebContentsView at 1280 x 800 -- capture - resize/encode - NativeKit read-only panel Chat BrowserWindow -- trusted chat renderer -- Browser panel: the same Agent WebContentsView when visible约束是「每页视图的原生父级数 1」panel 父级是聊天宿主 contentView render-host 父级是后台宿主 contentView1280 x 800 detached 无原生父级打开面板时是把这同一个页面视图 reparent 进可见面板URL、DOM、滚动位置、cookie、可聚焦页面状态、CDP 身份全部原样保留——不重新导航、不复制状态、不新建 CDP target。父级切换与捕获状态迁移都归主进程所有切换规则是「先移除旧父级再加新父级」并用当前 epoch 使过期的异步捕获工作失效。ensurePreviewHost的实现印证了这些约束YoBrowserPresenter.tshost new BaseWindow({ x: -10000, y: -10000, // 屏幕外 width: PREVIEW_VIEWPORT.width, // 1280 height: PREVIEW_VIEWPORT.height, // 800 show: !isMac, opacity: 0, focusable: false, skipTaskbar: true, hiddenInMissionControl: true, frame: false, transparent: true }) host.setIgnoreMouseEvents(true)值得注意的一个平台细节写在源码注释里macOS 上隐藏宿主再「解除节流」会破坏捕获引用了 Electron PR 行为所以 macOS 走host.showInactive()保持帧活跃而其它平台在宿主隐藏时才调用setBackgroundThrottling(false)。宿主closed事件里会同步释放 claim、复位previewMode为stopped、递增previewEpoch并恢复背景节流——这就是「主进程不等渲染进程清理就处理宿主销毁」的落地方式。预览模式、资格与捕获管线预览把「捕获」与「页面渲染」解耦为三种模式setPreviewModeYoBrowserPresenter.ts是它们的唯一入口capturing维护渲染宿主在资格满足时发布有界帧rendering页面继续对 Agent 工具可用但抑制预览捕获例如用户正要点「在面板中打开」stopped停止捕获并按页面生命周期释放预览宿主。资格条件全部满足才捕获页面归当前 Agent run 所有、会话处于工作working状态、拥有前台的聊天窗口、右侧面板关闭、当前 run 未被用户关闭并且持有当前进程级预览 claim。刷新图像不会从另一个预览来源如 Computer Use手中抢走所有权。「run 相关性」规则也很明确Agent run 在第一次浏览器动作之后才变得相关——run 一启动就展示空 PiP 是不允许的权限/提问暂停不是终态结果暂停、恢复、取消、被顶替supersession全程 run 标识与会话状态必须保持一致。捕获常量集中在 YoBrowserPresenter.ts是规格「有界捕获」条款的直接落地const PREVIEW_VIEWPORT { width: 1280, height: 800 } // 后台页面 viewport const PREVIEW_FRAME { width: 400, height: 250 } // 预览输出尺寸 const PREVIEW_ACTIVE_INTERVAL_MS 500 // 活跃期节奏 const PREVIEW_IDLE_INTERVAL_MS 2000 // 空闲期节奏 const PREVIEW_MAX_BYTES 512 * 1024 // 编码输出上限 512 KiBcapturePreviewFrameYoBrowserPresenter.ts的单轮流程是校验previewMode capturing、epoch 未变、run 与 claim 仍是当前值、页面未销毁任一失败直接放弃本轮capturePage(1280×800 区域, { stayHidden: true })抓帧 →resize(400×250)→toJPEG(72)仅当 JPEG 字节数 ≤ 512 KiB 才推送推给native-overlay走previewCoordinator.present推给renderer-canvas走渲染进程 IPC 事件browser.preview.frame整轮结束后才安排下一轮 tick活跃页面加载中或处于 1.5 秒活动爆发期用 500ms否则用 2000ms。由此实现规格里的三条硬约束同一时刻最多一个在途捕获previewCapture单槽位 finally里才安排下一个 tick不合格或已停止的目标不做空闲捕获下一轮调度前再次isCurrent校验编码超界的帧被丢弃而不是截断。epoch 机制则保证任何「捕获/换父/停捕」发生时先前在途的异步帧要么因 epoch 不匹配被丢弃、要么因isCurrent校验失败被拒收——这正是「用当前 epoch 使过期异步捕获工作失效」的实现。渲染进程侧的 AgentBrowserPiP.vue 负责资格推导与模式合并eligible组合了requiresRenderingworking owneragent 有 run 标识 页面已初始化、窗口聚焦、面板关闭、未关闭当前 run 四个条件AgentBrowserPiP.vuepreviewMode计算属性把「面板打开 / 页面已可见 / 不合格」映射为rendering把「合格且非紧凑」映射为capturing。所有模式请求并不逐条透传而是写进pendingPreviewRequest由drainPreviewRequests串行合并发出AgentBrowserPiP.vue——这就是规格中「coalesces preview-mode requests」的落点。会话切换时会把旧会话的stopped请求发出且任何帧解码前的身份/epoch/序列号校验frameDecodeVersion、latestFrameSequence确保切换到别的会话时绝不会短暂暴露上一个会话的帧、URL 或标题。进程级呈现所有权AgentPreviewCoordinatorAgentPreviewCoordinatorsrc/main/desktop/preview/AgentPreviewCoordinator.ts是 Browser 与 Computer Use 共享的原生呈现仲裁器封装 NativeKit 的 overlayzerob13/nativekit。其目标身份AgentPreviewTarget是六元组source / windowId / sessionId / runId / epoch / claimSequence——任何一维不匹配帧推送就被isCurrent拒收。核心机制有四个claim所有权声明claim()按会话发号nextClaimSequence同一会话的新 run 顶替旧 run 的 claimmatchesClaim要求 claim 的 source、run、序号三方一致。「刷新图像不抢所有权」由这条链路保证。dismissedRunsrun 级关闭dismiss()把sessionId - runId记入dismissedRuns该 run 之后的任何prepare都直接返回none用户关闭 PiP 只压制当前 run不关页面、不取消工具、不中断 run。平台门控isPublishedTargetAgentPreviewCoordinator.ts仅放行darwin:arm64 / darwin:x64 / win32:x64 / linux:arm64 / linux:x64其余平台标记unavailable并记录unsupported_platform随后同一激活路径退回 Browser 侧面板。宿主同步overlay 通过attachHost绑定聊天窗口锚定在窗口后缘偏移 16pxAgentPreviewCoordinator.ts窗口 focus/blur/show/hide/minimize/move/resize/closed 事件驱动「模糊即隐藏、恢复即重新同步 bounds」50ms 防抖显示器增删/度量变化触发重新钳制。面板工具栏由协调器统一配置AgentPreviewCoordinator.tsbrowser 来源提供Open in side panelcontrol idopen-panel触发activate动作与Close触发dismiss两个按钮Computer Use 来源只有Close。双触发通道是overlay 的activate宿主区域被点与control事件都经emitAction派发到对应来源注册的 handler——Browser 侧的 handler 是 YoBrowserPresenter.ts 里的handleNativePreviewAction它在处理前先做 windowId/sessionId/runId/epoch/claimSequence 五元组校验随后停止捕获、把模式切到rendering、发browser.preview.action事件让渲染进程打开面板。坐标与性能预算方面协调器还携带一组常量AgentPreviewCoordinator.tsPREVIEW_MAX_EDGE 360overlay 尺寸上限、HOST_ANCHOR_OFFSET 16、HOST_SYNC_DELAY_MS 50、SLOW_PUSH_WARNING_MS 25单次pushImage超过 25ms 会打一条限频告警60 秒内最多一条。帧推送以presentationIdagent-preview:browser:windowId:sessionId保持稳定身份图像替换不重置用户拖拽过的原点——窗口移动/缩放同步与图像替换是两条独立路径。生命周期事件—呈现—页面行为对照表规格给出的完整生命周期矩阵是验收契约的核心此处完整保留事件呈现行为页面行为面板打开时首个 Agent 动作不显示 PiP激活 Browser在面板 bounds 稳定后附着同一页面面板关闭时的首个合格 Agent 动作仅在拿到有效帧后显示页面留在渲染宿主中原生能力不可用请求 Browser 侧面板激活通过常规面板路径附着同一页面Browser 面板打开隐藏原生预览并停止捕获reparent 进面板用户从 Browser 切回 Workspace面板保持打开期间不显示 PiP按活动 run 需要 detach 或保留后台渲染合格 run 中面板关闭显示前先推一帧当前帧reparent 回渲染宿主PiP 关闭压制当前 run 并停止捕获保留页面与活动工具原生「在面板中打开」或双击聚焦宿主窗口并请求 Browser面板 bounds 稳定后 reparent宿主失焦/隐藏/最小化同步隐藏仅当 Agent 工作需要时才保留渲染合格宿主恢复前台重新评估所有权并显示当前帧不重新加载页面run 进入终态停止捕获并移除呈现释放渲染宿主保持常规页面生命周期会话/页面销毁或宿主关闭移除呈现与目标释放所拥有的页面/宿主资源应用退出只停一次原生 overlay走既有幂等的 presenter 清理shutdown销毁全部会话浏览器并注销 preview handler「首帧先于可见」规则在两侧都有落点协调器present()内只有在desiredVisible isHostEligible时才setVisible(true)而捕获失败时保留上一张同目标有效图并在下一个有界 tick 重试绝不闪空图或切目标。失败处理与安全约束规格对失败与安全的条款在源码中逐条可查初始化/宿主附着失败 ⇒ 进程级禁用原生能力并打开 Browser 面板。disableNativetoolbar_update_failed/host_attach_failed路径置unavailable true并stopNative()一次性「坏帧」不会改道到renderer-canvas——renderer-canvas只对其既有调用方可用不是失败自动降级项YoBrowserPresenter.ts 的openBrowserPanelForUnavailablePreview只在!isAvailable()时激活面板。拿不到稳定面板 bounds ⇒ 保留/恢复预览并报告可恢复的交接失败不重载页面、不重复附着。远端页面失败走既有 Browser 错误行为与工具错误YoBrowserUnavailableError见 src/main/desktop/browser/YoBrowserToolHandler.ts预览失败不打断正常 Agent 操作。执行隔离页面保持在既有 sandbox/session 中执行sandbox: true 独立分区persist:yo-browseryoBrowserSession.ts原生呈现层只拿到缩放后的 JPEG 图像渲染进程或远端内容拿不到任何 NativeKit 对象或原生句柄。帧只在内存中不进入日志、缓存、持久化或磁盘文件日志只有有界的生命周期/性能元数据如slow frame presentation的durationMs不含图像字节、页面内容或密钥。YoBrowserOverlayWindow与 PiP 是两层前者仍是可见面板上的 Agent 活动图层点击/滚动/键入的位置标注独立于 PiP 存在。诚实性条款源可见性、帧时序、原生移动与打包合成器行为有待平台 QA配置了捕获间隔不等于对帧率的测量承诺。安全配置层面独立分区会话还会拒绝所有权限请求摄像头、地理位置等一律callback(false)并用webRequest.onBeforeSendHeaders强制覆盖 UA——UA 来自process.versions.chrome的桌面形态macOS/Windows/Linux 各有对应字符串这解释了规格「窄页面 bounds 不切换移动/触摸语义」整条链路上不存在按视口宽窄切换 UA 的逻辑。推迟的多标签工作区契约规格把「可见标签条 展开页面适配」明确排除在已交付的单页产品之外但完整保留了其验收前置条件供后续实现对照标签身份与归属只有当主进程所有者、类型化路由、渲染端调用方与生命周期测试一起迁移时才允许用「每会话标签集合 选中标签身份」替换单页状态用户标签与主 Agent 自动化标签严格区分不把用户标签静默变成 Agent 标签同一会话复用一个主 Agent 标签跨 URL 加载与 run不为每次load_url建标签精确 run 标识要贯穿 immediate/batch/resumed/deferred 工具路径关闭标签只销毁其页面并向并发操作返回可恢复的 page-closed 结果关 PiP 不关标签驻留/驱逐需要实测内存证据禁止臆造的闲置驱逐或重启持久化。响应式 chrome以容器宽度非应用视口为准——≥640px 支撑完整标签/导航行480–639px 用紧凑标签与溢出控件更窄时保留地址栏与键盘可达命令420px 面板必须可用。标签标题截断、标签滚动而非挤压、地址栏可收缩但不裁切导航命令Expanded panel ---------------------------------------------------------------- | [User tab] [Agent tab *] [] | | [Back] [Forward] [Reload] [URL ] [Fit] [Expand] | ---------------------------------------------------------------- Compact panel ---------------------------------------------- | [User] [Agent *] ... | | [Back] [Reload] [URL ] [...]| ----------------------------------------------页面呈现与展开响应式渲染用真实原生内容 bounds、默认 100% 缩放不按横向溢出静默缩放更不假装窄表面是移动浏览器Fit desktop是每标签的显式选择必须在证明布局/媒体查询、可读性、用户输入、CDP 坐标、截图尺寸、每标签隔离与跨导航/reparent 的确定性复位之后才可用——同源 Chromium zoom 行为不构成「每标签 fit」的证明坐标证明通过之前 Fit desktop 保持不可用Expand复用既有侧面板/全屏外壳占据聊天内容区配明确的 Restore chat 动作与焦点恢复不新建窗口、页面或 CDP target。回归保护与未决验证回归测试的落点由规格指定且仓库中均存在test/main/desktop/browser/YoBrowserPresenter.test.tspresenter 的捕获/换父/生命周期、test/main/desktop/preview/AgentPreviewCoordinator.test.ts按来源的预览协调器、test/renderer/components/AgentBrowserPiP.test.ts渲染端 PiP 资格与合并。实现必须持续保护的属性页面/CDP 身份保持、来源/run 隔离、有界捕获、渲染端拿不到原生帧载荷、稳定原生位置、安全面板交接、run 级关闭、确定性拆除。规格同时诚实列出了未决验证面取消/失败/顶替与暂停/恢复路径会话/路由/多窗口竞态宿主 show/hide/minimize/restore缩放/显示比例变化页面与宿主崩溃用户与 Agent 并发操作物理平台的焦点、z-order、拖拽、键盘与无障碍行为。Windows/Linux 打包检查、受支持 macOS 检查、原生移动/帧延迟测量在证据记录前保持开放任何实现变更仍须通过仓库格式、i18n、lint、typecheck 与相关聚焦测试。非目标明确不做原生视频 PiP、documentPictureInPicture或全帧率共享纹理视频管线多个同时可见 PiP或仅由用户操作的浮动页面向远端页面转发 PiP 输入或让渲染进程持有远端 WebContents隐藏页面的自动缩放、移动端 UA 切换、未测量的标签驱逐持久化 PiP 几何、通用化独立浏览器标签架构、公开预览框架。小结DeepChat 的 Agent 浏览器 PiP 是一套「契约先行、单页现实、原生呈现」的分层设计YoBrowserPresenter以每会话一份的SessionBrowserState保证页面身份唯一与父级数 ≤1捕获管线用固定 viewport、有界 JPEG、单在途 tick 与 epoch 校验把帧流约束在可验证的预算内AgentPreviewCoordinator用 claim epoch dismissedRuns 的六元组身份仲裁进程内唯一可见的原生呈现并让 Browser 与 Computer Use 共享所有权而不合并契约。规格文档、NativeKit PiP 架构契约 与三个测试文件共同构成了从产品行为到实现证据的完整闭环。【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考