
AIRI Desktop Grounding 只读 Chrome 扩展从 DOM 观察到 macOS 桌面坐标映射的落地实现【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi本篇技术指南以 AIRI 仓库中 chrome-extension/README.md 为骨架结合仓库源码background.js、msg_bridge.js、content.js及computer-use-mcp服务端的 extension bridge、grounding 层深入讲解AIRI 如何通过一枚 Manifest V3 Chrome 扩展以纯只读方式收集浏览器所有 frame 内的可交互元素并把它对接进 desktop grounding 快照解析体系最终让 AIRI Agent 用真实的 macOS 系统级输入事件CGEvent完成点击、输入等操作。读完本文你将掌握这套「DOM 观察桥 WebSocket 桥 桌面坐标映射」的完整链路以及每一层的源码级实现细节。一、这是什么一枚「只观察、不操作」的 DOM 桥AIRI Desktop Grounding Chrome 扩展定位为桌面 grounding 层的只读 DOM 观察桥Read-only DOM observation bridge。它解决的核心问题不是怎么点击网页而是网页里到底有哪些东西可以点、它们在哪里。它的职责被明确限定为三件事从当前活动 Chrome 标签页的**所有 frame含跨域 iframe**中收集可交互元素按钮、链接、输入框等上报元素的位置、ARIA role、文本与 rect 坐标把这些数据喂给 desktop grounding 的 snap resolver用于后续的坐标映射。与职责同样重要的是不做什么。原文档明确列出四条红线❌ 不做任何 DOM 变更不在 DOM 元素上 click / typing / scrolling❌ 不使用eval、new Function、chrome.scripting.executeScript❌ 不发起任何外部网络请求没有 Python bridge没有 offscreen documents❌ 没有 popup UI。所有用户交互由 desktop grounding executor 通过**真实的 macOS 系统级输入事件CGEvent**执行。也就是说这条链路上看与做被彻底分离扩展只负责看动手的事交给 macOS 原生输入层。这与仓库主 READMEservices/computer-use-mcp/README.md中区分 desktop 控制与 browser DOM 控制而不是什么都用盲点击的设计原则完全一致。二、三层架构两个隔离世界之间的纯中继原文档给出了简洁的架构图结合源码可以还原出完整的消息流background.js (Service Worker) ↕ chrome.tabs.sendMessage msg_bridge.js (ISOLATED world) ↕ window.postMessage content.js (MAIN world, window.__AIRI_DG__)理解这套三层结构的关键在于 Chrome 扩展的**隔离世界isolated worlds**机制chrome.runtime.onMessage只能在ISOLATED world被接收而window.__AIRI_DG__运行在MAIN world它需要直接访问真实 DOMgetBoundingClientRect、querySelectorAll等都依赖页面上下文两个世界无法直接共享作用域只能通过window.postMessage通信。于是msg_bridge.js就成了一名纯粹的信使msg_bridge.js它接收来自 background 的CU_ACTION消息为每个请求分配reqId__cu_req_${seqId}通过window.postMessage({ type: __CU_CALL__, reqId, method, args }, *)转发给 MAIN world 的content.jscontent.js 执行完方法后回发__CU_REPLY__bridge 再通过sendResponse把结果送回 background。如果 8 秒内没有回复bridge 会清掉挂起请求并返回{ success: false, error: timeout }。之所以要绕这一圈而不是让 background 直接调用__AIRI_DG__正是两个 world 的边界决定的——这也解释了为什么 manifest 中需要注册两个 content script。Manifest 关键配置manifest.json 是 MV3 规范值得逐项拆解{ manifest_version: 3, name: AIRI Desktop Grounding Bridge, permissions: [activeTab, tabs, webNavigation], host_permissions: [all_urls], background: { service_worker: background.js }, content_scripts: [ { matches: [all_urls], js: [content.js], run_at: document_idle, all_frames: true, match_about_blank: true, world: MAIN }, { matches: [all_urls], js: [msg_bridge.js], run_at: document_idle, all_frames: true, match_about_blank: true, world: ISOLATED } ] }要点说明world: MAIN/ISOLATED同一套matches下注入两个脚本分别落在两个世界这是上述架构的声明式基础all_frames: truematch_about_blank: true保证about:blank与嵌套 iframe 内也能注入配合webNavigation权限才能拿到完整 frame 树权限最小化只申请activeTab、tabs、webNavigation没有scripting权限——从权限层面就杜绝了executeScript等 DOM 注入能力与只读定位互为印证background.js 头部注释也明确记录了从原扩展剥离executeScript等命令的过程。三、content.jsMAIN 世界里的只读观察 APIcontent.js 在 MAIN world 暴露window.__AIRI_DG__命名空间版本号1.0-airi-dg并用 IIFE if (window.__AIRI_DG__) return防止重复注入。元素描述器_describeElement这是整个观察体系的最小信息单元返回的字段直接决定了 Agent 能看到什么{ tag, id, name, type, className, // 截断到 120 字符 text, // textContent 截断到 120 字符 value, // 截断到 60 字符 href, placeholder, role, disabled, checked, visible, // rect 宽高 0 rect: { x, y, w, h } // getBoundingClientRect() 取整 }注意visible只做最朴素的宽高判空不涉及遮挡检测——这是成本与精度的权衡后续由 grounding 层的置信度与去重逻辑兜底。可交互元素收集_collectInteractiveElements默认上限MAX_INTERACTIVE 200选择器覆盖a, button, input, textarea, select, [rolebutton], [rolelink], [roletab], [rolemenuitem], [rolecheckbox], [roleradio], [onclick], [tabindex]收集时只保留visible元素返回按文档顺序截断到上限。只读方法族相对 README 表格更全原文档的命令表列出了 7 个命令而从源码看__AIRI_DG__实际还提供readInputValue与getComputedStyles连同waitForElement在 background 层实现共 10 个。逐一说明方法行为collectFrameDOM(opts)返回当前 frame 的url、title、frameName、frameOffsetInParent、bodyText默认取 body 文本前 3000 字符可includeText: false关闭与interactiveElementscollectChildFrames()描述当前文档内的iframe/frame外壳index、id、name、title、src、contentUrl、rect供 background 重建子 frame 视口偏移findElement(selector)单元素查找返回_describeElement描述findElements(selector, max)多元素查找默认上限 10getClickTarget(selector)返回元素描述 中心点坐标x left width/2getElementAttributes(selector)遍历el.attributes返回全部属性调试用readInputValue(selector)只读 input/textarea/select 当前值密码框自动脱敏为[redacted]超 60 字符标记valueTruncatedcheckbox/radio 附带checkedselect 附带selectedIndex/selectedTextgetComputedStyles(selector, properties)读取计算样式不传properties时返回受控默认集display、visibility、opacity、position、width、height、color、font-size、overflow、z-index、pointer-events、cursor 等 14 项避免倾倒整个 CSSStyleDeclarationcollectFrameDOM里的frameOffsetInParent很关键顶层 frame 返回{x:0, y:0}子 frame 尝试通过window.frameElement.getBoundingClientRect()直接拿到自己在父文档中的位置跨域访问失败时返回null此时回退到 background 的启发式匹配见下一节。四、background.jsService Worker 里的调度中枢background.js 承担了三类职责WebSocket 桥接、命令路由、跨 frame 坐标还原。WebSocket 桥与重连退避扩展通过ws://127.0.0.1:8765直连computer-use-mcp的本地 WebSocket 服务服务端实现见 extension-bridge.ts。连接建立后立即发送hello握手const AIRI_BRIDGE_HELLO { type: hello, source: airi-chrome-extension, version: 1.1.0, }服务端收到hello后会记录lastHellosource、version、connectedAt作为桥接状态的一部分。断线后采用指数退避重连从BRIDGE_RECONNECT_MIN_MS 1000起每次翻倍封顶BRIDGE_RECONNECT_MAX_MS 10000onStartup、onInstalled与模块加载时都会主动触发ensureBridgeConnected()。请求采用id 配对background 为每个命令生成idsendBridgePayload发送{id, action, ...payload}收到{id, ok, result}或{id, ok:false, error}后按 id 匹配解决。单次命令超时SEND_CU_ACTION_TIMEOUT_MS 8000。命令路由内外两个入口handleCommand以switch(action)分发全部命令入口有两个chrome.runtime.onMessage收到{ type: AIRI_DG_COMMAND, data }AIRI 桌面端直接调用{ type: ws-incoming, data }兼容旧版 WebSocket 桥格式响应通过ws-send回发。这解释了原文档background.js (Service Worker)一层的落点它既可以是 WebSocket 服务端的对端也可以作为 runtime 消息入口两种通道最终都汇聚到同一套handleCommand。所有 DOM 查询命令都经由runCUAction→sendCUAction走向chrome.tabs.sendMessage(tabId, { type: CU_ACTION, method, args }, { frameId }, callback)向指定 tab frame投递sendMessage的frameId参数与webNavigation.getAllFrames枚举出的 frame 树配合实现全 frame 广播runCUAction缺省 frameIds 时自动枚举全部 framePromise.all并发执行。跨域 iframe 的坐标还原本文最精妙的部分webNavigation.getAllFrames只能给出 frame 树的父子关系与 URLChrome 并不暴露 iframe 在父文档中的屏幕位置background.js 注释直言这是浏览器限制。AIRI 的解法是两段式壳元素锚点收集对每个目标子 frame 的父 frame 调用collectChildFrames即 content.js 的_collectChildFrames拿到父文档中所有 iframe 壳的 rect启发式最佳匹配pickBestChildAnchor用加权打分把 frame 树节点匹配回父文档中的 iframe 壳contentUrl与子 frame URL 完全一致100src一致90子 URL 以 src 开头70framename与壳name一致40标题一致15唯一子 frame 且父文档只有一个壳10得分 0 才算命中。之后buildFrameOffsets用带缓存的递归从 frame 0视口原点向下逐层累加子 frame 的绝对偏移 父 frame 的绝对偏移 本 frame 的frameOffsetInParent或匹配到的壳 rect 原点。这样即使跨域 iframe 拿不到frameElement也能通过 URL/name/title 的相似度推断出它在页面里的物理位置。等待元素waitForElementbackground 层实现了一个轮询式等待以 500ms 为间隔反复调用各 frame 的findElements(selector, 1)直到任一 frame 命中或超时超时时间被钳制在 500ms30s 之间剩余预算会透传给每帧的 sendMessage 超时对应服务端WAIT_FOR_ELEMENT_BRIDGE_TIMEOUT_GRACE_MS 1000的宽限设计注释特别说明这样做是为了避免慢帧额外吃掉一整个 send-message 超时。五、服务端extension bridge 与桌面 grounding 快照WebSocket 服务端computer-use-mcp 侧extension-bridge.ts 的BrowserDomExtensionBridge监听ws://127.0.0.1:8765默认维护pending请求表每个请求登记{resolve, reject, timeoutId}通过randomUUID()生成的 id 与扩展端握手。它内置SUPPORTED_ACTIONS白名单与本文介绍的命令一一对应对不在白名单的 action 直接拒绝且连接断开时统一 reject 所有挂起请求。服务端还提供clickSelector组合操作先调getClickTarget拿到元素中心点再把它包装成clickAt坐标命令。注意——扩展本身并不实现clickAt它不在只读白名单内这套坐标会在更上层交给桌面 executor 以 macOS 系统事件执行形成DOM 定位 OS 事件触发的分工。配置通过环境变量控制详见 services/computer-use-mcp/README.md环境变量默认值作用COMPUTER_USE_BROWSER_DOM_BRIDGE_ENABLEDtrue开关COMPUTER_USE_BROWSER_DOM_BRIDGE_HOST127.0.0.1监听地址COMPUTER_USE_BROWSER_DOM_BRIDGE_PORT8765监听端口COMPUTER_USE_BROWSER_DOM_BRIDGE_TIMEOUT_MS10000请求超时若改动了 HOST/PORT需要用chrome.storage.local.set({ browserDomBridgeHost, browserDomBridgePort })在扩展侧同步让 background 重连到正确的 socket。语义适配与屏幕坐标映射chrome-semantic-adapter.ts 把扩展上报的页面相对坐标变换为屏幕绝对坐标优先走扩展桥captureViaExtension数据更丰富、无需--remote-debugging-port失败时回退 CDP 桥坐标变换screenX windowBounds.x frameOffsetX rect.xscreenY windowBounds.y 88 frameOffsetY rect.y其中CHROME_CHROME_HEIGHT_PX 88是对 Chrome 浏览器外壳标签栏 地址栏 书签栏高度的启发式估计注释明确说明实际值随缩放、书签栏显隐而变化v1 阶段以常量近似明显越界完全在窗口范围外的元素被过滤同时为每个元素构建最佳 CSS selector优先级#id [name] tag[type] tag.className供后续 DOM 级精确操作复用置信度打分button/a/显式交互 role 为 0.95表单控件 0.9checkbox/radio 0.85禁用元素降到 0.3其余 0.7。进入桌面 grounding 快照desktop-grounding.ts 是聚合层captureDesktopGrounding并行执行截图、窗口观察observeWindows、AX 树捕获并在 Chrome 处于前台时并入语义数据captureChromeSemantics最终产出统一的DesktopGroundingSnapshot候选融合与去重buildTargetCandidates把chrome_dom候选排在最前源排序chrome_dom ax vision raw当 chrome_dom 候选与 AX 候选的边界框 IoU 重叠超过70%时删除 AX 重复项chrome_dom 信息更丰富快照文本化formatGroundingForAgent生成对 LLM 友好的紧凑文本——前台应用、快照 id 与时间、过期警告截图/AX/Chrome 语义超过 2s 阈值标记stale、目标候选表[id] source role label (x,y w×h) conf0.xx最多列 40 个候选过期标记STALENESS_THRESHOLD_MS 2000超过该时长的子快照被标记提示 Agent 该数据可能已失真。六、开发模式安装步骤按原文档的流程即可加载打开chrome://extensions/开启右上角Developer mode开发者模式点击Load unpacked加载已解压的扩展程序选择本仓库的services/computer-use-mcp/chrome-extension/目录扩展会自动注入所有页面all_urlsall_frames。随后启动computer-use-mcp服务pnpm -F proj-airi/computer-use-mcp start扩展的 background service worker 会自动连上ws://127.0.0.1:8765并完成 hello 握手。可以用 extension-bridge.test.ts 中的测试思路做端到端自检该测试启动真实 WebSocketServer 客户端先发 hello 握手再用 mock action 验证请求 id 配对、超时拒绝、ok:false错误传播等行为。七、安全边界与已知限制把整条链路合起来看安全模型是层层递进的扩展层只读无 DOM 变更、无eval/executeScript、无外部请求、无 popup从权限声明到方法面双重限制交互走 OS 事件点击、输入由 macOSCGEvent注入且受computer-use-mcp的策略模型约束——点击/输入/滚动默认走 per-action 审批denyApps仍拦截敏感前台应用默认含1Password、Keychain、System Settings、Activity Monitor、AIRI自身终端命令与应用打开/聚焦必须审批bridge 白名单服务端SUPPORTED_ACTIONS兜底未知 action 直接报错。已知限制仓库 README 明示v1 主链路仅 macOS全局坐标被允许因此安全边界依赖审批 审计而非严格的应用隔离CHROME_CHROME_HEIGHT_PX 88是启发式常量iframe 匹配是相似度启发式而非精确几何定位。这些都意味着该扩展只解决观察与定位这一环真正把页面状态变成可控、可审计、可追溯的 Agent 操作靠的是computer-use-mcp整套执行与审批基座。八、小结一条可复用的浏览器 → 桌面接地链路AIRI 的这枚 Chrome 扩展展示了一个值得借鉴的模式用最小权限的只读观察桥把浏览器 DOM 变成可寻址的目标空间再用 OS 级输入事件完成真实交互。三层架构Service Worker / ISOLATED 中继 / MAIN 观察 API、双 world 的 postMessage 接力、基于 frame 树的启发式坐标还原、以及扩展桥与 grounding 快照的语义适配共同构成了一条从网页里有什么到它在屏幕哪里再到如何以真实输入触达的完整链路。读者可以在仓库中顺着 chrome-extension 目录逐文件对照阅读并配合 extension-bridge.ts、chrome-semantic-adapter.ts、desktop-grounding.ts 三个服务端模块把这条链路的每一环吃透。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考