@omniaibot/macos-arm64 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,128 @@
1
+ # TMWebDriver SOP
2
+
3
+ - 直接用browser_scan/browser_execute_js工具。本文件只记录特性和坑。
4
+ - 底层:`../TMWebDriver.py`通过Chrome扩展接管用户浏览器(保留登录态/Cookie)
5
+ - 非Selenium/Playwright,保留用户浏览器登录态
6
+
7
+ ## 通用特性
8
+ - ⚠browser_execute_js里使用`await`时需**显式`return`**才能拿到返回值(底层async包裹,不写return则返回null)
9
+ - ✅browser_scan自动穿透同源iframe;跨域iframe需CDP或postMessage(见下方章节)
10
+
11
+ ## 限制(isTrusted)
12
+ - JS事件`isTrusted=false`,敏感操作(如文件上传/部分按钮)可能被拦截;这类场景首选**CDP桥**
13
+ - ⚠JS点击按钮打不开新tab→可能是浏览器弹窗拦截,换CDP点击试试
14
+ - Vue3自定义组件(Select/Dropdown):⭐优先vnode实例调用(无视口限制)→见**vue3_component_sop**;CDP坐标点击仅适合选项少且可见的场景
15
+ - 文件上传:⭐首选**DataTransfer API**(纯JS,无CDP依赖):`new File([content],name,{type}) → new DataTransfer().items.add(file) → input.files=dt.files → dispatch input+change`;CDP `DOM.setFileInputFiles` 在tmwd桥环境nodeId跨调用失效,不推荐;备选ljqCtrl物理点击
16
+ - 需转物理坐标时:`physX = (screenX + rect中心x) * dpr`,`physY = (screenY + chromeH + rect中心y) * dpr`;其中 `chromeH = outerHeight - innerHeight`
17
+
18
+ ## 导航
19
+ - `browser_scan` 仅读当前页不导航,切换网站用 `browser_navigate` 或 `browser_execute_js` + `location.href='url'`
20
+
21
+ ## Google图搜
22
+ - class名混淆禁硬编码,点击结果用 `[role=button]` div
23
+ - browser_scan过滤边栏,弹出后用JS:文本`document.body.innerText`,大图遍历img按`naturalWidth`最大取src
24
+ - "访问"链接:遍历a找`textContent.includes('访问')`的href
25
+ - 缩略图:`img[src^="data:image"]`直接提取;大图src可能截断用`return img.src`
26
+
27
+ ## Chrome下载PDF
28
+ 场景:PDF链接在浏览器内预览而非下载
29
+ ```js
30
+ fetch('PDF_URL').then(r=>r.blob()).then(b=>{
31
+ const a=document.createElement('a');
32
+ a.href=URL.createObjectURL(b);
33
+ a.download='filename.pdf';
34
+ a.click();
35
+ });
36
+ ```
37
+ 注意:需同源或CORS允许,跨域先导航到目标域再执行
38
+
39
+ ## Chrome后台标签节流
40
+ - 后台标签中`setTimeout`被Chrome intensive throttling延迟到≥1min/次,扩展脚本中避免依赖setTimeout轮询
41
+ - 某些SPA页面需CDP `Page.bringToFront`切到前台才会加载数据
42
+
43
+ ## CDP桥(浏览器扩展) ⭐首选
44
+ 扩展路径:`browser-extension/`(需从仓库获取,含debugger权限)
45
+ ⚠TID约定标识:首次运行自动生成到`browser-extension/config.js`(已gitignore),扩展通过manifest引用
46
+ 优先使用 MCP 工具:`browser_batch`。必要时也可用 `browser_execute_js` script直传JSON字符串(工具层自动识别对象格式,走WS→background.js cmd路由)
47
+ ```js
48
+ browser_batch commands='[{"cmd":"cdp","method":"Page.bringToFront","params":{}}]'
49
+ browser_batch commands='[{"cmd":"cdp","method":"DOM.getDocument","params":{"depth":1}}]'
50
+ browser_execute_js script='{"cmd":"contentSettings","type":"automaticDownloads","setting":"allow"}'
51
+ browser_execute_js script='{"cmd": "tabs"}'
52
+ // 返回值直接是JSON结果
53
+ ```
54
+ 通信方式:⭐MCP 专用工具(首选) | JSON字符串直传 | TID DOM方式(TID元素+MutationObserver,browser_scan/browser_execute_js底层依赖)
55
+ 单命令:`{cmd:'tabs'}` | `{cmd:'cookies'}` | `{cmd:'cdp', tabId:N, method:'...', params:{...}}` | `{cmd:'management', method:'list|reload|disable|enable', extId:'...'}`
56
+ - management:list返回所有扩展信息;reload/disable/enable需传extId
57
+ - contentSettings:`{cmd:'contentSettings', type:'automaticDownloads', pattern:'https://*/*', setting:'allow'}`
58
+ - 绕过Chrome"下载多个文件"对话框(该对话框会阻塞整个浏览器JS执行)
59
+ - type可选:automaticDownloads/popups/notifications等;setting:allow/block/ask
60
+ - ⚠CDP的Browser.setDownloadBehavior在扩展中不可用(chrome.debugger仅tab级),此为替代方案
61
+ - ⭐batch混合:`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
62
+ - 返回`{ok:true, results:[...]}`,一次请求多命令,CDP懒attach复用session
63
+ - 子命令会自动继承外层batch的tabId(如cookies命令可正确获取当前页面URL)
64
+ - `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
65
+ - ⚠batch前序命令失败时,后续`$N`引用会静默变成undefined;要检查results数组中每项的ok状态
66
+ - 典型文件上传:getDocument(**depth:1**) → querySelector(`input[type=file]`) → setFileInputFiles
67
+ - 思想:
68
+ - 同一链路内保持nodeId来源一致,不混用querySelector路径与performSearch路径
69
+ - 上传后前端框架可能不感知,必要时JS补发`input`/`change`事件
70
+ - 上传前检查`input.accept`;多input时用accept/父容器语义区分
71
+ - 等待元素优先用`DOM.performSearch('input[type=file]')`做轻量轮询
72
+ - 瞬态input的核心是**缩短发现→setFileInputFiles时间窗**:优先同batch完成;再不行用DOM事件监听;猴子补丁仅作兜底思路
73
+ - ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
74
+ - ⭐跨tab无需前台:指定tabId即可操作后台标签页
75
+
76
+ ## CDP点击完整生命周期(✅已验证)
77
+ - 通用点击需**三事件序列**:mouseMoved → mousePressed → mouseReleased(间隔50-100ms)
78
+ - 省略mouseMoved会导致MUI Tooltip/Ant Design Dropdown等hover依赖组件失效
79
+ - ⚠autofill释放是特例,只需mousePressed即可(见下方autofill章节)
80
+ - ⭐**坐标系结论**:稳定状态下 CDP坐标 = `getBoundingClientRect()` 坐标,**无需修正**
81
+ - ⚠**首次attach陷阱**:CDP debugger首次attach时Chrome弹出infobar("正在受自动化控制",~20px高),页面内容被推下
82
+ - 如果在attach前测量坐标、attach后发送点击 → 坐标偏移!(之前Currency下拉失败的根因)
83
+ - ✅**解决**:确保测量坐标在CDP已attach稳定之后(即infobar已出现后再getBoundingClientRect)
84
+ - 实践:首次CDP操作前先发一个无害的`mouseMoved(0,0)`预热,之后坐标系就稳定了
85
+ - ⭐**下拉框(Vue3 oxd-select等)CDP操作流程**:
86
+ 1. 获取select元素rect → CDP点击打开下拉
87
+ 2. 获取option元素rect → CDP点击选中(option是动态DOM,打开后才能测量)
88
+ - 已验证:CDP点击对自定义下拉框有效,无isTrusted问题
89
+ - ⚠**限制**:选项多时底部option超出视口,CDP坐标够不着→此时应优先vnode方案(见vue3_component_sop)
90
+ - 坐标修正(页面有transform:scale/zoom时):
91
+ ```js
92
+ var scale = window.visualViewport ? window.visualViewport.scale : 1;
93
+ var zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
94
+ var realX = x * zoom; var realY = y * zoom;
95
+ ```
96
+ - iframe内元素CDP点击:坐标需合成 `finalX = iframeRect.x + elRect.x`
97
+ - 跨域iframe拿不到contentDocument:
98
+ - ⚠`Target.getTargets`/`Target.attachToTarget`在CDP桥中返回"Not allowed"(chrome.debugger权限限制)
99
+ - ⭐**已验证方案**:`Page.getFrameTree`找iframe frameId → `Page.createIsolatedWorld({frameId})`获取contextId → `Runtime.evaluate({expression, contextId})`在iframe中执行JS
100
+ - batch链式引用:`$0.frameTree.childFrames`遍历找url匹配的frame,`$1.executionContextId`传给evaluate
101
+ - postMessage中继方案仅在content script已注入iframe时有效,第三方支付iframe通常无注入
102
+
103
+ ## CDP文本输入(未验证,BBS#23)
104
+ - `insertText`快但无key事件;受控组件需补dispatch `input`事件
105
+ - 需完整键盘模拟时用`dispatchKeyEvent`逐键派发
106
+
107
+ ## CDP DOM域穿透 closed Shadow DOM(未验证,BBS#24/#25)
108
+ - `DOM.getDocument({depth:-1, pierce:true})` 穿透所有Shadow边界(含closed)
109
+ - `DOM.querySelector({nodeId, selector})` 定位 → `DOM.getBoxModel({nodeId})` 取坐标
110
+ - getBoxModel返回content八值[x1,y1,...x4,y4],中心用**四点平均**:centerX=sum(x)/4, centerY=sum(y)/4
111
+ - ⚠不能简化为对角线平均——元素有transform:rotate/skew时四点非矩形
112
+ - querySelector**不能跨Shadow边界写组合选择器**,需分步:先找host再在其shadow内找子元素
113
+ - ⚠nodeId在DOM变更后失效 → 用`backendNodeId`更稳定,或重新getDocument刷新
114
+
115
+
116
+ ## autofill获取与登录
117
+ 检测:browser_scan输出input带`data-autofilled="true"`,value显示为受保护提示(非真实值,Chrome安全保护需点击释放)
118
+ - ⚠**前置条件:必须先CDP `Page.bringToFront` 切tab到前台**,Chrome仅在前台tab释放autofill保护值,后台tab物理点击无效
119
+ - ⭐**一键释放与登录**:bringToFront → mousePressed点任一字段(无需Released,一个释放全页) → 等500ms → 补input/change事件 → 点登录
120
+
121
+ ## 验证码/页面视觉截图
122
+ - ⭐首选CDP截图:`Page.captureScreenshot`(format:'png')→返回base64,无需前台/后台tab也行,全页高清
123
+ - 验证码canvas/img:JS `canvas.toDataURL()` 直接拿base64最干净
124
+
125
+ ## simphtml与TMWebDriver调试
126
+ - simphtml调试必须通过`code_run`注入JS到真实浏览器(Python端无法模拟DOM)
127
+ - `d=TMWebDriver()`, `d.set_session('url_pattern')`, `d.execute_js(code)` → 返回`{'data': value}`
128
+ - simphtml:`str(simphtml.optimize_html_for_tokens(html))` — 返回BS4 Tag需str()
@@ -0,0 +1,163 @@
1
+ # Vue 3 自定义组件 JS 操作 SOP
2
+
3
+ ## 问题
4
+ Vue 3 自定义组件(如 OxdSelect)通过 `addEventListener` 绑定事件,JS `dispatchEvent` 产生的事件 `isTrusted: false`,组件不响应。
5
+ - `element.click()` 无效(组件可能绑定 mousedown 而非 click)
6
+ - `dispatchEvent(new MouseEvent('mousedown'))` 无效(isTrusted:false)
7
+ - `element.focus()` 不触发 Vue 绑定的 focus handler
8
+
9
+ ## 解决方案:直接操作 Vue 组件实例
10
+
11
+ ### 1. 获取 Vue 3 根入口
12
+ ```javascript
13
+ const rootVnode = document.getElementById('app')._vnode;
14
+ ```
15
+
16
+ ### 2. 遍历 vnode 树匹配 DOM 元素
17
+ ```javascript
18
+ function findCompByEl(vnode, targetEl, depth = 0) {
19
+ if (depth > 50 || !vnode) return null;
20
+ const comp = vnode.component;
21
+ if (comp) {
22
+ if (comp.vnode?.el === targetEl || comp.subTree?.el === targetEl) return comp;
23
+ if (comp.vnode?.el?.contains?.(targetEl)) {
24
+ const result = findCompByEl(comp.subTree, targetEl, depth + 1);
25
+ if (result) return result;
26
+ return comp;
27
+ }
28
+ const subResult = findCompByEl(comp.subTree, targetEl, depth + 1);
29
+ if (subResult) return subResult;
30
+ }
31
+ if (vnode.children && Array.isArray(vnode.children)) {
32
+ for (const child of vnode.children) {
33
+ const result = findCompByEl(child, targetEl, depth + 1);
34
+ if (result) return result;
35
+ }
36
+ }
37
+ if (vnode.dynamicChildren) {
38
+ for (const child of vnode.dynamicChildren) {
39
+ const result = findCompByEl(child, targetEl, depth + 1);
40
+ if (result) return result;
41
+ }
42
+ }
43
+ return null;
44
+ }
45
+ ```
46
+
47
+ ### 3. 调用组件方法
48
+ ```javascript
49
+ // 目标DOM的parentElement通常是组件根元素
50
+ const comp = findCompByEl(rootVnode, targetElement.parentElement);
51
+ const ctx = comp.proxy;
52
+
53
+ // 查看可用方法
54
+ Object.keys(ctx).filter(k => !k.startsWith('_') && !k.startsWith('$'));
55
+
56
+ // Select 类组件:直接调用 onSelect
57
+ ctx.onSelect({id: 'USD', label: 'United States Dollar'});
58
+
59
+ // 获取选项列表
60
+ ctx.computedOptions; // [{id, label, _selected}, ...]
61
+ ```
62
+
63
+ ## 组件层级注意
64
+ - **展示层**(如 OxdSelectText):只有 onToggle/onFocus/onBlur,调用无实际效果
65
+ - **逻辑层**(如 OxdSelectInput,是展示层的父组件):有 openDropdown/onSelect/computedOptions/onCloseDropdown
66
+ - 定位逻辑层:用 `targetElement.parentElement` 而非 targetElement 本身
67
+
68
+ ### 弹窗内 Select 同样纯 JS 优先(已验证)
69
+ - 弹窗(`.oxd-dialog-sheet`)内的 `.oxd-select-text` 用循环向上查找同样能命中 `OxdSelectInput`,`onSelect` 正常工作。
70
+ - 不需要 CDP 兜底。仅当循环 8 层仍找不到组件时才考虑 CDP 打开+JS 点 option。
71
+
72
+ ### 循环向上查找模式(推荐)
73
+ 单层 `parentElement` 可能不够,用循环更健壮:
74
+ ```javascript
75
+ function findSelectComp(selectTextEl) {
76
+ for (let el = selectTextEl, up = 0; el && up < 8; el = el.parentElement, up++) {
77
+ const comp = findCompByEl(rootVnode, el);
78
+ if (comp?.proxy?.onSelect && comp.proxy.computedOptions?.length) return comp;
79
+ }
80
+ return null; // 找不到再考虑CDP兜底
81
+ }
82
+ ```
83
+
84
+ ## 普通 Input/Textarea 操作(nativeSetter)
85
+
86
+ Vue 3 的 `v-model` 监听 input 事件,直接 `el.value = x` 不触发响应式。需用原型 setter:
87
+
88
+ ```javascript
89
+ // Input
90
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set;
91
+ setter.call(inputEl, '新值');
92
+ inputEl.dispatchEvent(new Event('input', {bubbles: true}));
93
+ inputEl.dispatchEvent(new Event('change', {bubbles: true}));
94
+
95
+ // Textarea
96
+ const taSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
97
+ taSetter.call(textareaEl, '内容');
98
+ textareaEl.dispatchEvent(new Event('input', {bubbles: true}));
99
+ ```
100
+
101
+ ### Date Input 特殊处理
102
+ 日期组件通常有 blur 校验,需要 focus→赋值→blur 完整链:
103
+ ```javascript
104
+ dateInput.focus();
105
+ setter.call(dateInput, '2026-08-05');
106
+ dateInput.dispatchEvent(new Event('input', {bubbles: true}));
107
+ dateInput.dispatchEvent(new Event('change', {bubbles: true}));
108
+ dateInput.dispatchEvent(new Event('blur', {bubbles: true}));
109
+ ```
110
+
111
+ ### Button
112
+ 普通 `.click()` 即可,Vue 3 不检查 button click 的 isTrusted。
113
+
114
+ ### File Upload (input[type="file"])
115
+ 浏览器安全模型禁止JS直接 `input.value='path'`,但可用 DataTransfer API 构造 FileList:
116
+ ```javascript
117
+ const fileInput = document.querySelector('input[type="file"]');
118
+ const content = '文件内容';
119
+ const file = new File([content], 'filename.txt', { type: 'text/plain', lastModified: Date.now() });
120
+ const dt = new DataTransfer();
121
+ dt.items.add(file);
122
+ fileInput.files = dt.files; // Chrome 62+ 支持
123
+ fileInput.dispatchEvent(new Event('input', { bubbles: true }));
124
+ fileInput.dispatchEvent(new Event('change', { bubbles: true }));
125
+ ```
126
+ - 适用于任何框架(非Vue3特有),纯浏览器API
127
+ - 可构造任意类型文件(Blob/ArrayBuffer均可传入File构造器)
128
+ - ⚠ CDP `DOM.setFileInputFiles` 只设files属性不触发事件(Chrome通用行为),DataTransfer+dispatch是唯一纯JS方案
129
+ - ⚠ 确保弹窗/容器已打开再querySelector,否则input不在DOM中
130
+
131
+ ## 泛化到其他 Vue3 站点(未逐一验证,思路层面)
132
+
133
+ 本 SOP 的核心方法(根 vnode → findCompByEl → proxy)是 Vue3 通用的,但具体方法名/属性名因 UI 库而异。
134
+
135
+ 面对陌生 Vue3 站点的探测思路:
136
+
137
+ 1. **确认是 Vue3** — `document.getElementById('app')?.__vue_app__` 存在即可
138
+ 2. **定位目标 DOM** — 用选择器找到要操作的元素(如某个 select wrapper)
139
+ 3. **从 DOM 反查组件** — 用 findCompByEl 从目标元素及其父级向上找,拿到 component
140
+ 4. **探测组件能力** — 拿到 comp 后查看:
141
+ - `Object.keys(comp.proxy.$options.methods || {})` → 组件方法名
142
+ - `Object.keys(comp.props || {})` → props
143
+ - `Object.keys(comp.setupState || {})` → setup 暴露的响应式数据和函数
144
+ - 重点找类似 onSelect/handleSelect/select/setValue 的方法,以及 options/items/computedOptions 之类的选项列表
145
+ 5. **试调** — 找到疑似选中方法后,传入选项对象试调,观察 DOM 是否更新
146
+ 6. **选项格式** — 不同库的 option 结构不同(可能是 `{id, label}` 也可能是 `{value, text}` 或纯字符串),从选项列表数据中取一个完整对象传入即可
147
+
148
+ 注意事项:
149
+ - 有些库用 `emits` 而非 methods,选中逻辑可能在父组件而非子组件
150
+ - 有些库 prod build 会 minify 方法名,此时 setupState 里的 key 可能是短名,需结合行为猜测
151
+ - Composition API 组件的逻辑主要在 setupState 而非 $options.methods
152
+ - 如果 proxy 上找不到方法,试试 `comp.exposed`(`<script setup>` 用 defineExpose 暴露的)
153
+
154
+ ## 适用场景
155
+ - Vue 3 自定义 Select/Dropdown/Autocomplete 组件 → vnode 实例方法
156
+ - Vue 3 普通 Input/Textarea(v-model)→ nativeSetter + input 事件
157
+ - Date 组件 → nativeSetter + focus/blur 链
158
+ - File Upload → DataTransfer + change 事件
159
+ - 需要绕过 isTrusted 检查的场景
160
+
161
+ ## 验证于
162
+ - OrangeHRM (opensource-demo.orangehrmlive.com) Vue 3 + OXD 组件库
163
+ - 2026-05-08
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@omniaibot/macos-arm64",
3
+ "version": "0.3.6",
4
+ "description": "omnibot executable for macOS ARM64",
5
+ "bin": {
6
+ "omnibot": "bin/omnibot-macos-arm64/omnibot-macos-arm64"
7
+ },
8
+ "os": [
9
+ "darwin"
10
+ ],
11
+ "cpu": [
12
+ "arm64"
13
+ ],
14
+ "files": [
15
+ "bin/"
16
+ ],
17
+ "keywords": [
18
+ "mcp",
19
+ "browser",
20
+ "automation",
21
+ "omnibot"
22
+ ],
23
+ "author": "Unagi-cq",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/DennisJcy/omnibot.git"
28
+ }
29
+ }