Merge remote main; re-apply remote port default 9222 to regenerated catalog

This commit is contained in:
yu 2026-11-06 13:10:10 +08:00
commit 1b952e3bbd
45 changed files with 2320 additions and 128669 deletions

View File

@ -252,3 +252,37 @@
- 坑:审计脚本初版误报 313 个——间接调用(selectorOf(args))读的 keys 提取不到;args.min_second 点号访问无引号,兜底正则必须两种形式都查
- 校验器 requiredInContext 已有条件豁免(designated_obj 仅 designated_info 开启时必填),目录改选填后 UI 红星同步消失
- 审计脚本留在 scripts/,后续新增组件可复跑防回归
## 项目清理(17:05-17:12)
- 用户要求清理无用文件/文档;扫描后分四组征求确认
- 直接删:根目录 3 个旧测试日志(.phase3-test2.log/.phase4-test.log/vitest-full.log)、.screenshots 空目录
- 用户确认删:temp/ 415MB(AI 拾取模型选型实验,代码零引用)、scripts/ 4 个无引用调试脚本(spawn-test/cdp-probe/desktop-e2e/export-last-run)、docs/ 8 份历史规划文档(01~06 编号系列+OCR 检测报告,git 历史可查)
- 用户选择保留:release/dist 1.8GB 打包产物
- README 有 5 处悬空引用(4 个文档链接 + cdp-probe),已同步改为三份活跃文档索引
- 提交 ec25db2,13 files、-3122 行;文档删前务必 grep 交叉引用
## git 追踪审查与模型移除(17:15-17:30)
- 审查 git 追踪的大文件:仅 resources/models 两个 onnx 共 176MB 占仓库 pack 148MB 大头;用户选择继续追踪、不改历史
- 顺带发现打包 bug:extraResources 漏拷 ui-icon-detect(5a3d4cb 补上)
- 用户随后确认 ui-icon-detect/model.onnx(80MB)无实际使用(仅老安装包兜底回退,webpage-element 永远先命中)→ 整目录删除 + 运行时去掉兜底候选(79bba42),打包拷贝行随之移除
- 坑:localUiModelPath 有第三顺位回退 app.getAppPath()/temp/models,指向刚删的 temp/ 目录——但 resources/models 优先级更高且模型齐全,dev 不受影响
## 仓库完整性核查(17:28-17:35)
- 用户问"仓库是否包含所有开发能用得上的" → 全面核查
- 结论:是。代码/文档/脚本/catalog/模型(webpage-element)/fixture/测试流程全在 git;node_modules、out、release 为可再生产物已忽略
- 发现并修复:captcha 端到端测试图原依赖仓库外 D:/office-test2(fresh clone 自动跳过跑不到)→ 5 张图(30KB)入库 __fixtures__/captcha,测试改 import.meta.dirname 解析,12/12 通过(dbe9894)
- 环境依赖(仓库外、有文档说明):RPA_ELECTRON_DIST(dev:local/start/demo-e2e 需要,因工作区 ACL 沙箱问题);真实桌面冒烟测试需 Windows 桌面(无 skip 守卫,属设计)
- ocr-smoke 的 3 张旧 fixture 图已不存在,该 describe 长期 skip,可考虑后续清理
## 迁入指南 V2 修订(次日 08:50-09:10,实为 11-06 会话)
- 用户要求按《RPA项目技术审查与Agent-Employee集成实施总方案》+真实源码全面修订迁入指南成 V2,只改文档不改源码
- 双侧并行探查核查:RPA 侧 14 项、AE 侧 10 项;关键人工复核(defaultEngine 捕获、driver 无 signal)
- 重大发现:AE 底座巨变——workflow-engine/capability-gateway/browser-plugin(Playwright!)/windows-runner(C# FlaUI observe-only) 都已实现;"空壳契约"叙事只剩 computer-control Runner 和 WindowsRunner.click/type
- RPA 侧确认:createEngine() 仍无注入参数、desktop.ts:9 模块加载时捕获 driver(非 win32 import 即抛)、5 处动态 electron import、check-deps 正则盲区实测、catalog 3 个 platformTag=1 脏值
- 交付:docs/rpa-core-migration-guide-v2.md(架构图/模块对应表/接入点 A1-A10 七要素/0-6 阶段/10 风险/10 验收/6 ADR+3 Q 待确认)+ v2-diff.md(V1 逐项判定 + 审查报告 12 条修正)
- 提交 a8f75c6 + 收录审查报告一笔
## ADR-R3/R6 拍板(11-06 09:40)
- 用户确认两项关键决策:①桌面点击逻辑改写进对面 C# 原生进程(PowerShell 通道仅限 demo);②按能力域拆分部署(纯 Node 域→backend plugin-worker,桌面/视觉/弹窗域→用户电脑 Electron 宿主)
- V2 文档已更新:§0 结论、§10 拆"已拍板/待评审"、A6 改哪侧、阶段 0 范围、风险 R1/R2 降级为已缓解
- 剩余 4 ADR(R1/R2/R4/R5)+ 3 Q 留待与对面对评审

View File

@ -73,18 +73,17 @@ src/main/fixture 本地演示站
src/preload 白名单桥
src/renderer React UI:画布、组件库、属性面板、运行/设置页
src/shared 主/渲染共享类型
scripts build-catalog / dev-safe / start / cdp-probe / 打包冒烟
scripts build-catalog / dev-safe / start / 打包冒烟
resources catalog.json(1016 项派生元数据)、夹具站(含元素演练场)、桌面桥
docs 现状分析、实施方案
docs 开发规范、迁移指南、重构任务清单
```
## 文档
- [开发规范](docs/dev-conventions.md)
- [rpa-core 迁移指南](docs/rpa-core-migration-guide.md)
- [重构任务清单](docs/rpa-core-refactor-tasks.md)
- [应用与系统 50 项组件测试任务](resources/test-flows/app-system/README.md):13 组可安装任务、使用步骤、覆盖清单及实际验收报告。
- [现状分析与可行性](docs/01-现状分析与可行性.md)
- [实施方案(三天)](docs/02-实施方案.md)
- [未接入能力名单](docs/04-未接入名单.md)
- [重复能力去留清单](docs/05-重复能力去留清单.md)
## 边界

View File

@ -1,334 +0,0 @@
# 画布式 RPA 桌面端 · 现状分析与可行性(调研稿)
> 编写时间:2026-10-26
> 参考目录:`D:\code\Agent-Employee`(只读)、`C:\rpa-demo`(只读)
> 本文件是决策输入,不是最终实施方案;带「待确认」的条目需要产品方拍板。
---
## 0. 一句话结论
`D:\code\Agent-Employee` 里的 **1016 项 RPA 目录全部是静态元数据**(997 个厂商组件定义 + 16 个原生控制节点 + 3 个项目自有触发器),**不含任何厂商运行时**;`C:\rpa-demo` 只有**浏览器**动作层(CDP 可信输入,26/26 自测通过),没有画布、没有桌面能力、没有执行引擎。
因此三天内**不可能**做出"1016 个能力都能跑"的产品。可行的目标是:
> **一个本地优先的画布式 RPA 桌面端纵向切片**:左侧 1016 项目录可搜索、可拖拽、可配置;画布支持连线/分支/循环/变量;点运行后由真实执行内核跑通一条端到端流程;未接入的组件在目录和节点上显式标记,运行时给出明确失败原因。
建议真实可执行组件目标 **100~140 项**(覆盖浏览器、文件、Excel、字符串/时间/列表、键鼠/窗口、HTTP/邮件),其余标记「目录可用、执行器待接入」。
---
## 1. 参考资产盘点
### 1.1 `D:\code\Agent-Employee`:产品全量仓库,RPA 只是其中一块
| 资产 | 位置 | 结论 |
|---|---|---|
| 1016 项目录 | `code/frontend/apps/desktop/src/renderer/product-preview/pages/workflows/shizai-catalog-data.js`(1.8 MB) | **可直接复用**:997 组件完整字段、默认值、分类、函数签名 |
| 目录投影/分组 | 同目录 `component-catalog.js` | 可复用 11 组分组规则、字段类型映射、3 个触发器、别名表 |
| 现有流程页 | `product-preview/pages/workflows/page.js`(50 KB) | 已有:搜索筛选、增删节点、属性面板、缩放、纵向流式画布、localStorage 草稿。**不足:不是自由画布,不能拖拽定位,没有执行器** |
| 设计原型 | `项目资料/UI设计/20261021/流程编排页原型.html` | 纵向线性流程,非拖拽画布;可作视觉基调参考 |
| 浏览器插件契约 | `code/backend/packages/browser-plugin/src/index.ts` | 只声明 `navigate/click/type/snapshot` 四个动作,`unsigned-development` |
| Windows 插件契约 | `code/backend/packages/windows-plugin/src/index.ts` | 只声明契约,无实现 |
| Windows Runner | `code/backend/services/windows-runner/native/*.cs` | **只读 UIA/截图观察**,注释明确"没有 SendInput / SetForegroundWindow / 进程启动" |
| 工作流引擎 | `code/backend/packages/workflow-engine/src/*` | 服务端 PostgreSQL 工作流运行/审批/恢复,**不适合本机三天产品**(要 Docker/PG/多服务) |
| 预览客户端 | `交付物/客户端/Windows/x64/0.1.0-preview.1/` | 已有 Electron 安装包,但只跑前端演示,不含 RPA 执行器 |
**判断**:目录元数据是最大可复用资产;现有画布是"线性步骤列表",与用户要的"自由拖拽画布"不是一回事;后端工作流栈不适合三天工期。
### 1.2 1016 项目录的真实构成
来自 `shizai-catalog-data.js` 的 `counts`(已用脚本解析核对):
| 项 | 数量 |
|---|---:|
| 厂商组件定义 | 997 |
| 原生控制节点(IF/循环/异常/子流程…) | 16 |
| 项目自有触发器(人工/定时/文件到达) | 3 |
| **合计** | **1016** |
| 普通输入字段 | 4258(其中必填 1318,占 31%) |
| 输出字段 | 690 |
| 选项字段 | 6148 |
| 唯一函数名 | 997(组件与函数 1:1) |
| 字段数为 0 的组件 | 22 |
| 字段数 1–3 / 4–8 / 9–15 / 16+ | 473 / 414 / 75 / 13 |
按产品分组(沿用现有 `component-catalog.js` 规则):
| 分组 | 组件数 | 说明 |
|---|---:|---|
| 网页自动化(WebBrowser + Element) | 187 | 浏览器与元素操作,最接近 rpa-demo 能力 |
| Office 与文档(Excel/Word/PDF/转换) | 230 | Excel 单独 170 项,是最大单一包 |
| 数据处理(表格/字符串/时间/列表/字典/数学/正则…) | 167 | 纯函数为主,最容易批量实现 |
| 图像与 OCR | 100 | 依赖视觉模型/厂商服务,三天不做 |
| 网络与消息(HTTP/邮件/通知/NLP) | 82 | HTTP/邮件可做,NLP 不做 |
| 桌面与键鼠(键盘/鼠标/窗口/剪贴板/对话框) | 79 | 需 Windows 原生桥 |
| 应用与系统(App/System/Basic/屏幕控制) | 60 | 部分可做(启动进程/环境变量/延迟/变量) |
| 文件与传输(File/FTP) | 46 | 可做文件系统,FTP 可选 |
| 移动与外设 | 46 | 需 ADB/Appium/机械臂,三天不做 |
| 编排控制 | 16 | IF/计次/条件/无限循环/遍历/异常/子流程/退出 |
| 触发器 | 3 | 人工/定时/文件到达 |
**关键事实**:`functions[].function`(如 `Excel.Cs_ReadContentV2`)只是**名称映射**,原厂商实现依赖 Python 包、原生 SDK、浏览器扩展、Office COM、云端 OCR 等,目录里没有一行可执行代码。仓库 README 也明确写着"不代表这些动作已经可执行"。
### 1.3 `C:\rpa-demo`:可复用的浏览器执行层
| 能力 | 事实 |
|---|---|
| 形态 | Electron 31.7.7 + 多标签 `WebContentsView` + 右侧任务面板 |
| 动作层 | `actions.js`:CDP `Input` 域可信输入,5 级定位(testid→aria→text→css→xpath),统一结果/错误码/后置条件/证据 |
| 流程 | `flows/*.json` 声明式步骤 + 顺序回放 + 失败即停 + 每步证据 |
| 拾取器 | 注入点击捕获,生成候选 locator |
| 安全 | 域名 allowlist、高影响动作开关、`actionId` 幂等去重 |
| 自测 | `selftest-out.txt`:**26/26 通过**(点击/输入/下拉/等待/表格/错误分类/证据/越界/流程回放) |
| 不足 | 只有浏览器;无画布;无变量系统(只有 runId 注入);无桌面/Office;单窗口任务面板是死脚本 |
**判断**:`actions.js` + 拾取器 + 流程回放语义可以整体搬进新产品,改造成本低,是三天内"真的能跑"的最大保障。
### 1.4 环境事实(本机)
| 项 | 结果 |
|---|---|
| OS | Windows 11 专业版 26200 |
| Node / npm / Bun | 22.13.0 / 10.9.2 / 1.4.3 |
| Python | `py -3.10` 可用;已装 numpy/Pillow/opencv/pyautogui/pywin32;**无** pandas/openpyxl/python-docx/PyMuPDF |
| Office COM | Excel、Word 已安装(`Excel.Application` / `Word.Application` 注册存在) |
| 显示 | 1920×1080,主屏 100% 缩放(125%/150% 仍需冒烟) |
| 网络 | npm 官方源与 npmmirror 均可达 |
| 磁盘 | C: 仅剩 ~9.9 GB;D: 剩 ~159 GB → **项目与依赖放 D:** |
| Electron | `C:\rpa-demo\node_modules\electron` 已下载 31.7.7,可离线复用 |
---
## 2. 能做 / 不能做(三天口径)
### 2.1 建议纳入"真实可执行"
| 层 | 能力 | 预计可执行组件 | 实现方式 |
|---|---|---|---|
| L0 控制 | IF、计次循环、遍历列表、条件循环、异常捕获、跳出/继续、退出、子流程、设置变量、延迟、日志 | 16 个控制节点 + 基础组件 | 自研解释器 |
| L1 浏览器 | 打开/导航/点击/输入/取文本/取属性/等待/存在判断/下拉/悬停/滚动/截图/表格提取/执行 JS/标签页/上传下载 | 60~80 | 复用 `actions.js` + CDP + 拾取器 |
| L2 文件 | 读/写/复制/移动/删除/重命名/存在/列表/建目录/压缩/解压 | 25~35 | Node `fs` + `adm-zip` |
| L3 Excel/PDF/Word | 新建/打开/读区域/写单元格/写区域/追加/保存/工作表增删改/查找;PDF 页数/文本/合并;Word 写入 | 35~45 | `exceljs` + `pdf-lib` + `pdfjs-dist`;Word 走 COM 桥 |
| L4 数据 | 字符串/时间/数学/正则/列表/字典/JSON/CSV/数据表筛选排序去重 | 50~70 | 纯 JS 函数库 |
| L5 桌面 | 键鼠输入/热键/坐标点击/窗口查找激活/窗口状态/进程启动/剪贴板/消息框/文件对话框/环境变量/命令执行 | 35~45 | PowerShell 5.1 桥(UIA + SendInput) |
| L6 网络 | HTTP 请求、SMTP 发信、FTP 上传下载(可选)、PostgreSQL(可选) | 8~15 | `fetch` / `nodemailer` / `basic-ftp` / `pg` |
**合计目标:约 110~150 项可执行**(按函数名注册,允许同类组件共享实现内核)。
### 2.2 明确不做(三天)
- 移动端/外设/机械臂(Mobile、peripherals,46 项)
- OCR、验证码、图像模板匹配、视觉定位(100 项;最多做"截图 + 区域保存")
- NLP/AIGC/云服务类(NLP、DataService、Scenario 等)
- SAP、IE、Java 应用、Chrome 扩展等厂商专有通道
- 并行分支、分布式执行、审批流、多租户、云端市场
- 厂商 Python 运行时对接(本机未安装该产品,且无授权运行时)
### 2.3 "未接入"组件的产品处理
建议(待确认):**目录全量保留 + 状态徽标**。
- 可执行:绿色圆点 + 可拖拽
- 待接入:灰色徽标「待接入」,可拖拽但节点置灰,运行时在日志中明确报 `capability_not_implemented`,不静默跳过
- 目录顶部显示统计:`1016 项目录 / 当前可执行 N 项`
- 属性面板仍可编辑(元数据完整),便于后续接执行器
这样既满足"1016 个能力都能拖"的观感,又不谎称可执行。
---
## 3. 推荐技术方案
### 3.1 形态
**新建独立 Electron 桌面应用(`D:\RPA`),不直接改 `Agent-Employee`。**
理由:
1. `Agent-Employee` 是 OpenCode v2 fork + 服务端栈,启动/构建/依赖都重;三天内集成会先耗在环境上。
2. 本产品需要主进程直接持有 `WebContentsView` 做 CDP、直接 `child_process` 调 PowerShell,独立应用最直接。
3. 目录元数据、画布视觉、动作语义都可复制/对齐,后续可平移回 `Agent-Employee`。
4. 交付物清晰:一个可双击的 exe + 源码 + 文档。
### 3.2 技术栈
| 层 | 选型 | 说明 |
|---|---|---|
| 壳 | Electron 31.7.7 | 复用本机已缓存版本,避免大版本差异 |
| 构建 | electron-vite + Vite + React 18 + TypeScript | 主进程/预加载/渲染进程一套工程 |
| 画布 | `@xyflow/react`(React Flow) | 拖拽、连线、缩放、MiniMap、分组容器,三天内唯一现实选择 |
| 状态 | Zustand + immer | 画布状态、运行状态分离 |
| 样式 | 原生 CSS 变量 + 设计令牌 | 对齐现有预览视觉(浅色、蓝灰、圆角) |
| 持久化 | 本地 JSON 文件(`userData/flows/*.json`)+ 运行记录 JSONL | 不引入数据库 |
| 执行 | 主进程 Node 解释器 + 能力注册表 | 与 UI 通过 IPC 事件流通信 |
| 桌面 | PowerShell 5.1 常驻子进程(JSON over stdio) | UIAutomation + user32 `SendInput`,免原生模块编译 |
| 浏览器 | 内嵌 `WebContentsView` + CDP | 搬 `actions.js` |
| 打包 | `@electron/packager` 产出 portable 目录/ZIP;有余量再加 NSIS | 避开 electron-builder 的签名工具下载风险 |
### 3.3 画布数据模型(草案)
```jsonc
{
"schemaVersion": 1,
"id": "flow_01",
"name": "网页订单录入",
"variables": [{ "name": "orderNo", "type": "string", "default": "" }],
"nodes": [
{ "id": "n1", "componentId": "Trigger.Manual", "position": { "x": 80, "y": 40 }, "values": {} },
{ "id": "n2", "componentId": "WebBrowser_CreateV3", "position": { "x": 80, "y": 180 },
"values": { "browserType": "内置浏览器" }, "outputVar": "web" },
{ "id": "n3", "componentId": "logic_control_if_else", "position": { "x": 80, "y": 320 },
"values": { "condition": "${amount} > 1000" } },
{ "id": "n4", "componentId": "Element_Click_V6", "position": { "x": 80, "y": 420 },
"parentId": "n3", "slot": "statement_if", "values": { "uiSelector": "…" } }
],
"edges": [{ "id": "e1", "source": "n1", "target": "n2", "sourceHandle": "out" }]
}
```
- **节点**:`componentId` 指向 1016 目录;`values` 是字段键值;`outputVar` 接收返回值。
- **容器**:IF/循环/异常节点通过 `parentId` + `slot` 形成子画布;React Flow 原生父子节点即可承载。
- **连线**:默认顺序边;IF 节点两个出口 `true/false`;循环体走容器子节点,循环后走 `after` 出口。
- **变量**:`${var}`、`${var.field}`、`${list[0]}`;表达式使用自研小型解析器(不用 `eval`)。
### 3.4 执行模型
```
校验(组件存在/必填/连线/环检测)
→ 编译(容器树 → 执行计划)
→ 逐节点执行(上下文:变量、密钥引用、浏览器会话、证据目录)
→ 每个节点产出 { status, output, logs, evidence, errorCode, durationMs }
→ 失败按节点策略(停止 / 重试 N 次 / 跳过 / 人工确认)
```
错误码封闭集合:`not_found / timeout / blocked_url / capability_not_implemented / expression_error / io_error / office_error / desktop_error / cancelled / unknown`。
**证据**:每步可截图(浏览器/桌面)、保存输出摘要;运行目录 `userData/runs/<runId>/`,路径只回传相对路径。
### 3.5 目录 → 表单 → 执行器
1. 启动时把 1016 项加载为 `ComponentDefinition`(离线 JSON,随包)。
2. 字段 `displayType`(200+ 种)归并为 12 类控件:文本/表达式、数字、下拉、单选、复选、文件、目录、多行、代码、密钥、元素选择器、区域选择;未知类型降级为文本并标注。
3. 执行器注册表以 `functions[].function` 为键:
```ts
register("StrUtil.SplitStr", (ctx, args) => { … })
register("Excel.Cs_ReadContentV2", async (ctx, args) => { … })
```
4. 未注册的函数在编译期标记 `implemented=false`,运行时返回 `capability_not_implemented`,并给出"已注册 N/997"的统计。
### 3.6 桌面桥(关键风险点)
PowerShell 5.1 常驻进程,stdin/stdout 行分隔 JSON:
| 命令 | 实现 |
|---|---|
| `win.find/activate/state/rect/close` | user32 `FindWindow/EnumWindows/SetForegroundWindow/ShowWindow` |
| `uia.find/invoke/setValue/getText` | `UIAutomationClient` 程序集 |
| `input.click/move/scroll/type/hotkey` | user32 `SendInput`(绝对坐标用 `MOUSEEVENTF_ABSOLUTE`) |
| `screen.capture` | `System.Drawing` 截屏 |
| `clip.get/set` | `System.Windows.Forms.Clipboard` |
要点:
- 启动时声明 Per-Monitor DPI Awareness,坐标统一用**物理像素**;Electron 侧按 `screen.getPrimaryDisplay().scaleFactor` 换算。
- UIA 操作优先(元素可访问),坐标点击作为兜底并标注风险。
- 目标窗口不在前台时先 `SetForegroundWindow`,失败要显式报错,不盲点。
### 3.7 打包与交付
- `npm run build` → `dist/win-unpacked/`,双击 `RPA Studio.exe` 启动。
- `npm run package` → ZIP + 版本清单 + SHA256。
- 交付:源码、可执行包、`README`、示例流程 2 条、自测报告、演示脚本。
---
## 4. 三天排期(含今天 2026-10-26)
> 假设:单人 + AI 辅助,今天可用约 12 小时,后两天各约 12 小时。每半天一个可验收产物。
### Day 1(10/26):地基 + 画布
| 时段 | 任务 | 验收 |
|---|---|---|
| 上午 | 工程脚手架(Electron+Vite+React+TS)、设计令牌、三栏布局、路由 | `npm run dev` 起窗口 |
| 下午 | 目录加载与 11 组分组、搜索/虚拟列表、拖拽入画布、节点卡片、连线/删除/框选、缩放/MiniMap | 能从 1016 项拖 5 个节点并连线 |
| 晚上 | 属性面板(12 类控件)、变量面板、流程保存/加载、撤销重做 | 刷新后流程还原 |
| 睡前 | 容器节点(IF/循环)父子拖放与几何 | 能把节点拖进 IF 分支 |
### Day 2(10/27):执行内核 + 浏览器 + 桌面
| 时段 | 任务 | 验收 |
|---|---|---|
| 上午 | 解释器(编译/执行/变量/表达式/错误策略/日志事件) | 直线流程可跑 |
| 下午 | 浏览器运行时(搬 `actions.js`)+ 元素拾取器 + 截图证据 | 打开网页、点击、输入、取文本 |
| 晚上 | 桌面桥(PowerShell)+ 键鼠/窗口/剪贴板/对话框 | 能操作记事本/计算器 |
| 睡前 | 数据/文件/字符串/时间 批量执行器(60+) | 单节点用例通过 |
### Day 3(10/28):Office/网络 + 联调 + 打包
| 时段 | 任务 | 验收 |
|---|---|---|
| 上午 | Excel/PDF/Word、HTTP/邮件执行器 | 读写 xlsx、发测试邮件 |
| 下午 | 运行面板(状态高亮/日志/变量/证据)、停止/重试、示例流程 | 端到端流程跑通 |
| 晚上 | 打包 portable、冒烟(100%/125%/150%)、README/演示脚本/自测报告 | 双击 exe 可演示 |
**若进度落后,砍功能顺序**:FTP/DB → Word/PDF → 邮件 → 桌面 UIA(保留坐标键鼠)→ 循环容器(保留 IF)→ 目录全量(退化为可执行子集)。
---
## 5. 建议的验收脚本(Demo)
**场景 A(主推,跨 Web + Office + 消息)**
1. 打开内置浏览器 → 访问本地夹具页/演示站
2. 填写表单(姓名/金额)→ 点击提交
3. 抓取结果表格 → 写入 Excel(新建/指定模板)
4. IF 金额 > 1000 → 发邮件/桌面通知;否则记录日志
5. 运行面板显示每步耗时、截图、变量值
**场景 B(纯桌面 + 文件)**
1. 启动记事本 → 输入文本 → 保存到指定目录
2. 读取目录内 xlsx → 筛选 → 另存为新文件
3. 弹窗提示完成
**画布验收**:1016 项可搜、11 组筛选、拖拽/连线/容器/变量/保存重载/运行高亮/失败定位。
---
## 6. 风险清单
| 风险 | 影响 | 缓解 |
|---|---|---|
| "1016 个组件"被理解成"1016 个都能跑" | 验收预期落差 | 目录徽标 + 启动页统计 + 运行时明确报错;文档写清边界 |
| 三天做不完 | 交付缩水 | 纵向切片优先;未完成项按"砍功能顺序"降级 |
| 桌面自动化 DPI/焦点/权限 | 演示失败 | PowerShell 桥显式 DPI、UIA 优先、坐标兜底;100/125/150 冒烟 |
| Excel COM 慢/弹窗/版本差异 | 卡住流程 | 文件级用 `exceljs`;COM 只用于"可见打开"演示 |
| 内嵌浏览器与用户 Chrome 差异 | 登录态/站点兼容 | 明确"内置浏览器",不做外部 Chrome 接管 |
| 未接入组件被拖入流程 | 运行中断 | 编译期阻断 + 画布红标 + 一键定位 |
| C 盘空间不足 | 构建失败 | 全部依赖与缓存放 D 盘;Electron 复用本地缓存 |
| 第三方包下载失败 | 阻塞 | 已确认 npm 源可达;关键包锁定版本并本地缓存 |
---
## 7. 待产品方确认(详见对话提问)
1. 基底:独立新项目 vs 并入 `Agent-Employee`
2. 三天验收重心:纵向切片 / 组件广度 / 画布完成度
3. 必须真实执行的类别
4. 未接入组件的处理方式
5. 画布控制结构深度(IF / 循环 / 并行)
6. 演示场景(网页+Excel+邮件 / 纯桌面 / 自定义系统)
7. 交付物形态(portable exe / 安装包 / 源码 / 视频)
8. 环境约束(可装 npm 包、可调 PowerShell、是否离线、缩放适配)
---
## 8. 参考文件索引
- 目录数据:`D:\code\Agent-Employee\code\frontend\apps\desktop\src\renderer\product-preview\pages\workflows\shizai-catalog-data.js`
- 目录投影:同目录 `component-catalog.js`
- 现有流程页:同目录 `page.js`、`index.html`
- 设计原型:`D:\code\Agent-Employee\项目资料\UI设计\20261021\流程编排页原型.html`
- 浏览器动作层:`C:\rpa-demo\actions.js`、`main.js`、`flows.js`、`selftest.js`
- 动作契约:`C:\rpa-demo\AI-接手开发指南.md`
- 浏览器插件契约:`D:\code\Agent-Employee\code\backend\packages\browser-plugin\src\index.ts`
- Windows 只读 Runner:`D:\code\Agent-Employee\code\backend\services\windows-runner\native\NativeWindows.cs`
- 厂商实现分析:`D:\code\Agent-Employee\项目资料\开发方案\实在ai分析结果\01-总览与结论\全功能实现逻辑总览.md`

View File

@ -1,661 +0,0 @@
# 画布式 RPA 桌面端 · 实施方案(三天)
> 制定时间:2026-10-26 09:50
> 依据:`D:\code\Agent-Employee`(只读)、`C:\rpa-demo`(只读)、2026-10-26 需求确认
> 本方案是执行基线:范围、契约、排期、验收、降级顺序都在这里;实施中变更必须回写本文件。
> **2026-10-30 当前状态**:常用本地能力已进入注册表或运行时容器,覆盖控制/触发、除数据库外的数据处理、文件、网页元素层级/表格/下拉框/浏览器控制/Cookie/下载及 DOM 高级能力、HTTP、Excel 常用读写/格式/筛选/历史版本、系统路径与环境变量、进程管理、桌面键鼠/窗口对象/剪贴板图片/对话框、PDF、Word 文档编辑、SMTP 与群机器人 Webhook、文本差异、工作日判断、屏幕截图、离线图像查找与点击、FTP、人机交互表单对话框、Windows 凭据/远程桌面/屏保、ECharts 图表读取、AI 文本能力(用户自配服务)、离线 OCR;启动后目录显示 **732 项可执行 / 1016 项总目录**。当前 26 个测试文件共 167 条测试,浏览器 E2E 真实结果为“订单创建成功:RPA 客户,金额 128”。数据处理仅剩 8 个待评估的数据库组件。设置页可查看分类覆盖和处理器 ID 数量。此后以集中场景测试与稳定性修复为主,不再追求厂商云端、硬件和专用通道数量。(原 2026-10-27 快照:619 项可执行 / 23 个测试文件 138 条测试)
> 浏览器端到端门禁:`npm run test:e2e` 使用隔离用户目录启动应用和本地演示站,真实执行打开网页、两次可信输入、点击提交和读取结果;期望结果必须精确包含“订单创建成功:RPA 客户,金额 128”。浏览器输入、点击、定位歧义和遮挡检查机制已对照 `C:\rpa-demo/actions.js` 校准。
---
## 0. 决策记录(2026-10-26 确认)
| # | 问题 | 结论 |
|---|---|---|
| 1 | 代码基底 | 在 `D:\RPA` 新建独立 Electron 应用 |
| 2 | 三天验收重心 | 一条真实流程端到端跑通 |
| 3 | 必须真实执行 | 浏览器网页、文件与 Excel、桌面键鼠/窗口/剪贴板/对话框、数据/字符串/时间/列表/正则、HTTP 与邮件、PDF/Word;图像/OCR「最好有」;移动端不做;数据库/FTP 靠后 |
| 4 | 未接入组件 | **全量 1016 可拖拽;未接入项灰标「待接入」,运行时明确报错**(2026-10-26 10:20 调整,覆盖早前"只导入可执行子集") |
| 5 | 画布深度 | 顺序 + IF + 循环容器 |
| 6 | 演示场景 | 待定;默认用本地夹具页,可替换为你的系统 |
| 7 | 元素拾取 | 浏览器必须;桌面手工填 |
| 8 | 交付物 | 可双击运行的 portable 包 + 自测报告(源码随仓库交付) |
| 9 | 约束 | 可装 npm 依赖;可用 PowerShell 做桌面自动化;**运行期完全离线**;适配 125%/150% 缩放 |
### 需要你知晓的三个默认取舍
1. **产品包含 1016 全量目录**:可执行项正常拖拽;未接入项灰标「待接入」,可拖入画布但运行时在该节点给出 `capability_not_implemented` 与替代建议。目录数据随包(约 1.8 MB)。
2. **OCR 不阻塞验收**:先做本地图像查找/点击(OpenCV 模板匹配,离线);OCR 用 Windows 自带引擎作为可选加分项,做不成就在自测报告中标注未交付。
3. **厂商资料只做行为参考**:`实在ai分析结果` 与 `Rea-AgentShizai-cankao` 用于校准流程格式、组件字段、执行语义、错误与选择器模型;不复制厂商代码/二进制/模型/前端资产(授权未确认,见复用审查)。
---
## 1. 交付定义(Definition of Done)
三天结束时,必须能在一台干净的 Windows 11 x64 机器上:
1. 解压 `RPA-Studio-0.1.0-win-x64.zip`,双击 `RPA Studio.exe` 启动,**无需联网、无需 Node/Python**(Word/图像为可选能力,缺依赖时显式降级)。
2. 左侧组件库可见 **1016 项**,按 11 组与「全部/可执行/待接入」筛选;可执行项正常拖拽,待接入项灰标但仍可拖入画布。
3. 新建流程 → 从左侧组件库拖入 ≥6 个能力 → 连线 → 配置参数、变量、超时/重试/失败策略 → 保存。
4. 点「运行」后流程真实执行,画布节点按状态高亮,运行面板显示每步日志/耗时/变量/截图;待接入节点在此处给出明确失败信息。
5. 跑通一条端到端流程:**内置浏览器打开本地夹具页 → 填表提交 → 抓取结果表格 → 写入 Excel → 按金额分支 → 桌面通知/发信**。
6. 故意制造失败(选择器不存在/文件不存在/待接入组件),流程在该节点停止,日志给出可分类错误码、原因、建议与证据。
7. 自测报告列出全部用例与通过情况,明确"可执行/待接入"清单。
**非目标(三天)**:移动端、机械臂、验证码、云端 OCR、并行分支、分布式、审批流、多租户、市场、录制器、AI 生成流程。
---
## 2. 范围基线
### 2.1 产品内可执行组件(计划 130,验收目标 120,保底 90;目录仍展示 1016 全量)
| 族 | 目标 | 代表组件 | 实现要点 |
|---|---:|---|---|
| 控制/触发 | 14 | `logic_control_if_else`、`logic_control_count_for`、`logic_control_array_for_v3`、`logic_control_while`、`logic_control_try_catch`、`logic_control_var`、`logic_control_break_for`、`logic_control_next_for`、`logic_control_stop`、`Trigger.Manual`、`Trigger.FileArrival` | 自研解释器;容器父子节点;IF 双出口;循环带最大次数保护 |
| 浏览器/元素 | 28 | `WebBrowser_CreateV3`、`WebBrowser_GoUrl`、`WebBrowser_Switch`、`Element_Click_V6`、`Element_SetValue_V6`、`Element_GetText_V5`、`Element_Exists`、`Element_SelectOperation_v1`、`WebBrowser_RunJs`、`WebBrowser_SaveAs` | 内嵌 `WebContentsView` + CDP;移植 `C:\rpa-demo\actions.js`;五级定位;拾取器 |
| 文件 | 14 | `File_Read`、`File_Write`、`File_Copy`、`File_File_Move`、`File_DeleteFile`、`File_Rename`、`File_FileExists`、`File_GetFileList`、`File_MakeDir`、`File_CompressFile`、`File_DecompressFile`、`File_GetFileInfo` | Node `fs` + `adm-zip` |
| Excel/CSV | 18 | `Excel_Cs_NewV5`、`Excel_Cs_OpenV4`、`Excel_Cs_ReadContentV2`、`Excel_Cs_WriteCell`、`Excel_Cs_WriteRange`、`Excel_Cs_ReadRangeV2`、`Excel_Save`、`Excel_Cs_SaveAs`、`Excel_GetSheetsName`、`Excel_Cs_Insert`、`Excel_Cs_Rename`、`CSV_Read`、`CSV_Write` | `exceljs` 文件级;COM 只用于"可见打开"演示 |
| 数据/文本 | 28 | `StrUtil_InStr/CutOutStrV1/SplitStr/Replace/CaseChange/Trim/Len/Left/Right/Mid`、`Time_GetDate/FormatNew/DateOffset`、`List_CreateList/InsertList/ListLength/ListSorted`、`Dict_Create/GetItem/SetItem/Keys`、`Regex_Test/FindAll`、`Math_Round/Abs`、`JSON_Parse/Stringify`、`Basic_SetVariable/ConvertNumber` | 纯 JS,表驱动批量接入 |
| 桌面 | 16 | `Keyboard_Send_V1`、`Keyboard_PressNew`、`Mouse_Click`、`Mouse_MoveTo`、`Mouse_DragTo`、`Mouse_RollTo`、`Window_SetActive`、`Window_GetActive`、`Window_WindowExists`、`Window_CloseWindow`、`App_Run`、`Clipboard_GetText`、`Clipboard_SetText`、`Dialog_Notice`、`Dialog_MsgBox`、`System_ExecuteCommand` | PowerShell 5.1 常驻桥:UIA + `SendInput` + user32 |
| 网络/邮件 | 4 | `Http_Get`、`Http_Post`、`Mail_Send`、`Mail_UnionSend` | `fetch` + `nodemailer` |
| PDF/Word | 5 | `PDF_GetPdfPageNum`、`PDF_GetPdfText`、`PDF_ConcatPdf`、`Word_New`/`Word_Write`(含保存) | `pdf-lib` + `pdfjs-dist`;Word 走 COM |
| 图像 | 3 | `Image_Screenshot`、`Image_Find`、`Image_Click` | 截屏 + 模板匹配(OpenCV/Python 3.10 已装) |
> 说明:
> 1. 注册数以"函数名 → 实现"计;同一实现可挂多个组件变体(如 `Element_Click_V6` 与其变体)。最终以自测报告为准,未通过的不计入;**保底 90 项**是验收底线。
> 2. 目录展示 **1016 全量**(997 + 16 + 3)。上表是"能真实执行"的子集;其余组件灰标「待接入」,属性面板仍可查看/编辑元数据,运行时给出 `capability_not_implemented`。
> 3. 组件字段与校验从 `componentsNew.json` 的 `definition.args0` 派生:`vType`(rpa_str/int/float/bool/list/dict/excel/hWeb/window/any)、`validatorRules`、`displayParam`、`tip`、`isAdvanced`。
### 2.2 明确不交付
- 移动端/外设/机械臂/U 盾/远程桌面(46+ 项)
- OCR 验证码、NLP、AIGC、云服务类组件
- SAP/IE/Java 应用/浏览器扩展通道
- 并行、子流程跨文件、审批、多租户、云同步
### 2.3 路线图(三天后)
| 阶段 | 内容 |
|---|---|
| P2 | 剩余组件接入;厂商 `.task` 导入/导出;完整选择器结构(commonProps/selector[])兼容 |
| P3 | 桌面 UIA 元素拾取、窗口选择器、图像 OCR、邮件 IMAP |
| P4 | 数据库/FTP/SAP、子流程与并行、审批与证据中心 |
| P5 | 与 `Agent-Employee` 的 Workflow/插件契约对齐、能力包分发 |
### 2.4 两份参考资产怎么用(2026-10-26 补充)
**结论:高价值行为参考,不做代码复制。** 它们解决四个关键问题:流程格式、组件定义、执行语义、错误与选择器模型。
| 资产 | 位置 | 用途 | 边界 |
|---|---|---|---|
| 复用审查(决策入口) | `实在ai分析结果/01-总览与结论/开发参考与复用审查.md` | 判断哪些机制可参考、哪些不能 | 厂商代码授权未确认,只参考机制 |
| 组件字典 | `Rea-AgentShizai-cankao/code/rpa-engine/componentDicts/componentsNew.json`(997 项) | 补全 `vType`、`validatorRules`、`displayParam`、`tip`、`isAdvanced`,驱动类型化表单与校验 | 仅构建期派生,不随包分发原始 JSON |
| 真实工程样例 | 同目录 `code/app/resources/examples/*.zip`(网页填表、新闻抓取、微信回复) | 对照 `.task` / `project.flow` / `globalVariable` / 生成的 `projects/flow_modules/*.py`,校准流程与执行语义 | 只读行为参考,不复制业务内容与截图 |
| 任务格式 | 样例包内 `*.task` | `componentModelEntities` + `MAIN_COMPONENT.statements` + `args0`/`options0`;据此定义节点 `options` 与容器 `statements` | 三天不做导入/导出 |
| 画布格式 | 样例包内 `project.flow` | 节点 x/y/width/height、shape、edges、anchors、`reTryCount`、`errorVar` | 厂商是"块级画布 + 块内步骤",我们是单层画布 |
| 执行与错误语义 | `Rea.../code/derived-engine-core`(203 份源)+ `rpa-engine/conf/err_code/err_codes.py` | 重试/超时/skip/log 规则、错误 reason+recommendation、元素变量插值 | 不复制实现;用公开库重写 |
| 选择器模型 | 样例 `globalVariable` 的 `SHIZAI_ELEMENT_DICT`;`common/util/elements_util.py` | `{commonProps, selector:[{name, props:[{name,value,pattern}]}]}`;`${}` 插值;pattern 枚举 | v1 存简化选择器,P2 兼容完整结构 |
| 组件包 | `rpa-engine/311_32/packages/*`(54 包,`.pyc` + `.pyi` + `config.json`) | 查函数签名、表单 schema、参数顺序 | 字节码不是源码,不复制 |
| 前端/插件 | `code/app/resources/*`、`code/support/*` | UI 交互、浏览器扩展安装、Office 插件行为参考 | 资产与二进制不复制 |
**采纳到契约里的具体设计:**
1. 节点 `options` 对齐 `options0`:`delayBeforeMs`、`delayAfterMs`、`timeoutMs`、`retryCount`(1..100)、`retryIntervalMs`(1..600s)、`skipOnError`、`logException`、`outputVar`。
2. 容器子节点保存为有序 `statements`(等价厂商 `input_statement`);画布仍用连线 + 父子关系,保存时编译为 statements。
3. 变量类型映射:`rpa_str/int/float/bool/list/dict/excel/hWeb/window/any` → 我们的 `string/number/boolean/list/dict/excel/browser/window/any`。
4. 错误对象扩展为 `{ code, reason, recommendation, location:{flowId,nodeId,componentId,arg} }`;失败日志展示"原因 + 建议 + 定位"。
5. 选择器 v1 存 `{kind:'css'|'aria'|'text', value, name?}`;拾取器同时产出厂商式候选,P2 再落完整 `commonProps/selector[]`。
6. 用真实样例做回归夹具:在本地夹具页复刻"读 Excel → 填网页 → 提交"的链路,验证语义,不复制内容。
7. 执行策略对齐:`skipOnError` 记错误日志并继续;`retryCount` 仅对可重试错误生效;参数校验失败不重试;`timeoutMs` 为整次调用总时限。
**明确不做:** 复制厂商 `.pyc/.dll/.exe/模型/前端资产`;调用厂商云服务;内置厂商运行时;三天内做 `.task` 导入/导出。
---
## 3. 产品形态与信息架构
```
┌──────────────────────────────────────────────────────────────┐
│ 顶栏:Logo | 流程名 | 保存状态 | 撤销/重做 | 缩放 | ▶运行 ■停止 │
├─────────┬──────────────────────────────────────┬─────────────┤
│ 左栏 │ 画布(点阵/节点/连线/容器/MiniMap) │ 右栏 │
│ 流程列表 │ │ 属性/变量/运行 │
│ 组件库 │ │ │
│ 变量 │ │ │
│ 运行历史 │ │ │
├─────────┴──────────────────────────────────────┴─────────────┤
│ 底部运行抽屉:时间 | 节点 | 状态 | 耗时 | 输出/错误 | 截图 │
└──────────────────────────────────────────────────────────────┘
```
页面:
1. **首页/流程列表**:新建、导入、最近运行、`1016 项目录 / 可执行 N 项`。
2. **编辑器**(核心):左组件库 + 中画布 + 右属性/运行。
3. **运行历史**:run 列表、状态、耗时、日志与证据查看。
4. **设置**:浏览器域名白名单、证据开关、超时、外观。
组件库展示 **1016 全量**,分组沿用 11 组;顶部显示「1016 项目录 · 可执行 N 项」,并提供「全部 / 可执行 / 待接入」筛选。可执行项正常拖拽;待接入项灰标、可拖入但运行时明确失败。
---
## 4. 技术架构
### 4.1 技术栈
| 层 | 选型 |
|---|---|
| 壳 | Electron 31.7.7(复用本机缓存) |
| 构建 | electron-vite + Vite + React 18 + TypeScript |
| 画布 | `@xyflow/react`(React Flow) |
| 状态 | Zustand(flowStore / runStore / uiStore) |
| 样式 | CSS 变量 + 设计令牌,无 UI 框架依赖 |
| 存储 | 本地 JSON(`userData`) |
| 测试 | Vitest(单元)+ `scripts/selftest.mjs`(Electron 主进程集成) |
### 4.2 目录结构
```
D:\RPA
├─ package.json
├─ electron.vite.config.ts
├─ resources/
│ ├─ catalog.json # 1016 全量 + 可执行状态 + 类型/校验元数据
│ ├─ fixture/ # 本地演示站点(离线)
│ └─ desktop-bridge.ps1 # 桌面自动化桥
├─ src/
│ ├─ shared/ # 类型、常量、错误码(主/渲染共用)
│ ├─ main/
│ │ ├─ index.ts # 生命周期、窗口、协议
│ │ ├─ ipc.ts # 类型化 IPC 路由
│ │ ├─ store/ # flows/runs/settings/secrets
│ │ ├─ engine/ # compiler/runtime/expression/context
│ │ ├─ capabilities/ # control/browser/files/excel/data/desktop/network/office/image
│ │ ├─ browser/ # view 管理、CDP、picker、evidence
│ │ ├─ bridge/ # PowerShell 客户端、Python 图像客户端
│ │ └─ fixture-server.ts
│ ├─ preload/index.ts # contextBridge 白名单 API
│ └─ renderer/
│ ├─ pages/ # Home/Editor/Runs/Settings
│ ├─ canvas/ # FlowCanvas/NodeCard/ContainerNode/Palette/edges
│ ├─ panels/ # Properties/Variables/RunPanel/Evidence
│ ├─ state/
│ └─ styles/
├─ examples/ # 示例流程 JSON
├─ scripts/ # build-catalog / selftest / package
├─ docs/
└─ release/ # portable 产物(不入 Git)
```
### 4.3 进程与边界
- **主进程**:唯一拥有文件系统、子进程、浏览器视图、执行引擎;渲染进程不能直接 `require`。
- **预加载**:`contextIsolation: true`,只暴露 `flow.* / run.* / catalog.* / browser.* / desktop.* / settings.*` 白名单方法。
- **渲染进程**:只做 UI 与状态;运行状态通过 `run:event` 订阅。
- **PowerShell 桥**:常驻子进程,行分隔 JSON;超时/崩溃自动重启一次并显式报错。
- **Python 图像桥**:按需启动,仅图像查找用;不可用时功能降级。
### 4.4 运行期离线
- 所有依赖打包进 asar/资源目录;不加载 CDN 字体/脚本。
- 内置浏览器只访问白名单(默认 `127.0.0.1`、`localhost`)。
- 夹具站由主进程在 `127.0.0.1` 随机端口启动。
- HTTP/邮件属于用户显式配置的外部系统,不是云依赖;演示默认用本地 mock。
---
## 5. 关键契约
### 5.1 流程定义(`FlowDefinition` v1)
```jsonc
{
"schemaVersion": 1,
"id": "flow_...",
"name": "网页订单录入",
"description": "",
"variables": [
{ "name": "amount", "type": "number", "default": 0, "scope": "flow" }
],
"nodes": [
{
"id": "n1",
"componentId": "Trigger.Manual",
"functionName": "Trigger.Manual",
"position": { "x": 120, "y": 80 },
"values": {},
"options": { "outputVar": "", "timeoutMs": 30000, "skipOnError": false }
},
{
"id": "n2",
"componentId": "logic_control_if_else",
"functionName": "logic_control_if_else",
"position": { "x": 120, "y": 260 },
"values": { "condition": "${amount} > 1000" },
"parentId": null
},
{
"id": "n3",
"componentId": "Element_Click_V6",
"position": { "x": 60, "y": 380 },
"parentId": "n2",
"slot": "statement_if",
"values": { "uiSelector": "css:#submit" },
"options": {
"outputVar": "",
"delayBeforeMs": 200,
"delayAfterMs": 200,
"timeoutMs": 15000,
"retryCount": 0,
"retryIntervalMs": 1000,
"skipOnError": false,
"logException": true
},
"enabled": true
}
],
"edges": [
{ "id": "e1", "source": "n1", "target": "n2", "sourceHandle": "out", "targetHandle": "in" }
],
"meta": { "createdAt": "...", "updatedAt": "...", "appVersion": "0.1.0" }
}
```
### 5.2 容器语义
| 节点 | 出口 | 子槽 | 执行 |
|---|---|---|---|
| `logic_control_if_else` | `true` / `false` | `statement_if` / `statement_else` | 条件为真走真槽链,否则走假槽链,之后汇合到主链下一节点 |
| `logic_control_count_for` | `after` | `count_for_fn` | 从 start 到 end 按 step 循环,每轮执行子链 |
| `logic_control_array_for_v3` | `after` | `array_for_fn` | 遍历列表/字典,暴露 `item`、`index` |
| `logic_control_while` | `after` | `if_for_fn` | 条件为真循环,最大 1000 次 |
| `logic_control_try_catch` | `after` | `try_fn` / `except_fn` / `else_fn` | 捕获异常,暴露 `${error}` |
子槽内节点按"连线优先、无连线按 y 排序"形成链;禁止跨槽连线;容器可嵌套。
### 5.3 表达式
- 文本字段支持 `${expr}` 插值;IF/循环条件为纯表达式。
- 语法:字面量、变量、成员/下标、算术/比较/逻辑/三元、括号。
- 函数白名单:`len, upper, lower, trim, replace, split, join, contains, startsWith, endsWith, matches, number, string, round, abs, min, max, random, now, today, formatDate, json, parseJson`。
- **不使用 `eval` / `new Function`**;自研 tokenizer + Pratt parser,错误定位到字符位置。
- 变量缺失:默认报 `expression_error`;可在设置中改为空串并告警。
### 5.4 运行事件
```ts
type RunEvent =
| { type: "run.started"; runId: string; flowId: string; startedAt: string }
| { type: "node.started"; runId: string; nodeId: string; attempt: number }
| { type: "node.log"; runId: string; nodeId: string; level: "info"|"warn"|"error"; message: string }
| { type: "node.finished"; runId: string; nodeId: string; status: NodeStatus; output?: unknown; errorCode?: string; durationMs: number; evidence: string[] }
| { type: "run.finished"; runId: string; status: "succeeded"|"failed"|"cancelled"; durationMs: number; error?: { nodeId: string; code: string; message: string } }
```
`NodeStatus = idle | running | succeeded | failed | skipped | cancelled`
### 5.5 错误模型
封闭 `code` 集合:
`not_found`、`timeout`、`ambiguous`、`blocked_url`、`capability_not_implemented`、`expression_error`、`validation_error`、`io_error`、`office_error`、`desktop_error`、`network_error`、`cancelled`、`unknown`
运行结果中的错误对象(对齐厂商 `err_codes.py` 的"原因 + 建议"风格):
```ts
type RunError = {
code: string;
reason: string; // 中文原因,如"未找到元素:css:#submit"
recommendation?: string; // 中文建议,如"检查选择器或等待条件"
location?: { flowId?: string; nodeId?: string; componentId?: string; arg?: string };
detail?: string; // 原始错误/堆栈,仅日志
};
```
### 5.6 能力注册表
```ts
type Capability = {
functionName: string; // 目录 functions[0].function
componentIds: string[]; // 允许挂载的组件
run(ctx: RunContext, args: Record<string, unknown>, node: FlowNode): Promise<CapabilityResult>;
// 可选:字段归一、选择器校验、证据策略
};
```
`capabilities/manifest.ts` 汇总所有已注册函数;`scripts/build-catalog.mjs` 生成 `resources/catalog.json`:**1016 全量** + `implemented: true|false` + 归一后的字段/类型/校验元数据。目录不再裁剪,未实现项由运行时守卫拦截。
---
## 6. 画布交互规格
### 6.1 组件库
- 搜索(中文名/英文 ID/标签/描述),11 组筛选,状态筛选(全部/可执行/待接入),虚拟滚动。
- 每项:图标、名称、ID、字段数、分类色条、状态点(绿=可执行,灰=待接入)。
- 拖拽:HTML5 `dragstart` 写入 `componentId`;画布 `drop` 用 `screenToFlowPosition` 定位。待接入项同样可拖拽。
- 双击:追加到当前选中节点之后。
- 目录为空/无匹配:空态提示。
### 6.2 节点
- 卡片:图标 + 名称 + 副标题(关键参数摘要)+ 输出变量胶囊 + 状态点 + 错误徽标。
- 待接入节点:整体降饱和 + 右上角「待接入」徽标 + tooltip「当前版本无执行器,运行到此会停止」。
- 选中/多选/框选;拖动;吸附网格;复制/粘贴/删除;右键菜单(复制、禁用、删除、查看定义)。
- 连接点:左侧输入,右侧输出;IF 两个输出(真/假);循环一个 `after` 输出 + 容器体。
- 连线:贝塞尔/折线;非法连接(自连、重复、跨容器、成环)即时拒绝并提示。
### 6.3 容器
- IF/循环以"分组节点"呈现:标题 + 折叠 + 子区域(虚线框)。
- 拖入子区域自动设置 `parentId` 与 `slot`;拖出清除。
- 容器高度随子节点自适应,可手动调整。
- 子槽为空时显示拖拽提示。
### 6.4 画布操作
滚轮缩放(0.3–2)、空格/中键平移、`Ctrl+0` 适配、MiniMap、背景点阵、框选、对齐辅助线(可选)。
快捷键:`Ctrl+S` 保存、`Ctrl+Z/Y` 撤销重做、`Delete` 删除、`Ctrl+D` 复制、`Ctrl+Enter` 运行、`Esc` 取消选择。
### 6.5 属性面板
Tab:属性 / 变量 / 运行。
属性分区:
1. **基本**:节点名称、组件 ID、启用、备注;待接入项显示「当前版本无执行器」。
2. **输入**:按目录字段渲染(12 类控件);必填标记;默认值;占位提示;高级字段折叠;字段类型来自 `vType`。
3. **输出**:输出变量名(自动建议)、变量类型(从 `vType` 推断)。
4. **失败策略**:超时(默认 30s,上限 7 天)、重试次数(0–100)、重试间隔(1–600s)、失败后跳过、记录异常、前置/后置延时。
5. **证据**:是否截图、是否保存输出。
字段控件映射(把 200+ `displayType` 归并):
| displayType 关键词 | 控件 |
|---|---|
| text-expression / text / coloredText / label | 文本 + 变量插入 |
| textArea / multipleTextArea / quillInput | 多行文本 |
| number / integerInputNumber / inputNumber / sliderBar / rangeInput | 数字 |
| dropDownSelect / defeasibleDropDownSelect / dynamicSelect | 下拉 |
| radioGroup / radio / radioLeft / buttonGroup / coloredRadio | 单选 |
| checkbox / switchControl / multifileSwitch | 布尔/多选 |
| selectFile / multiSelectFileOrDir / saveAsDialog / selectControllerFile | 文件/目录选择 |
| comboSelector / selectUi / uiwindowselector / phoneSelector | 元素选择器(浏览器拾取) |
| code / jsonEditor / codeInput | 代码/JSON 编辑器 |
| encryption / designPassword | 密钥(Electron safeStorage) |
| 其它 | 文本兜底 + "原始类型"提示 |
校验:必填、数字格式、变量名合法、选择器非空、文件路径存在(可关)。
---
## 7. 执行内核
### 7.1 编译期校验
1. 组件在目录中;未接入项标记 `pending`,允许保存;运行前提示「N 个待接入节点」,执行到该节点报 `capability_not_implemented` 并停止。
2. 必填字段非空(表达式允许 `${}`);类型与 `vType` 粗校验。
3. 失败策略范围:`timeoutMs` 0–604800000、`retryCount` 0–100、`retryIntervalMs` 1000–600000、延时 ≥0。
4. 连线合法:无环、无悬空、容器槽匹配。
5. 触发器唯一且在起点。
6. 变量引用可解析(未定义变量警告/错误可配)。
失败返回结构化 `validation_error[]`(含 `reason/recommendation/location`),画布定位到节点。
### 7.2 运行时
```
run(flow):
ctx = { vars: defaults, secrets, runId, signal, evidenceDir, logger }
plan = compile(flow)
emit run.started
try:
executeChain(plan.entry, ctx)
emit run.finished(succeeded)
catch AbortError: emit run.finished(cancelled)
catch e: emit run.finished(failed, node, code)
```
`executeChain`:顺序执行;遇容器递归;每节点:
1. 解析 `values`(表达式插值/类型转换)。
2. 应用 `delayBeforeMs`。
3. 执行 `capability.run`,总时限 `timeoutMs`;可重试错误按 `retryCount/retryIntervalMs` 重试(校验错误、`capability_not_implemented`、`blocked_url` 不重试)。
4. 写入 `outputVar`。
5. 应用 `delayAfterMs`;记录日志/证据/耗时。
6. 失败时:`skipOnError=true` → 记错误日志、节点状态 `skipped`、继续;否则按错误停止整个运行。
7. `logException=true` 时把堆栈写入运行日志(不写入 UI 摘要)。
### 7.3 取消
`AbortController` 贯穿:浏览器命令、PowerShell 请求、HTTP、文件循环都检查 `signal`;停止后不再启动新节点。
### 7.4 证据
- 浏览器/桌面节点可截图(PNG)。
- 每节点输出 JSON 摘要(截断 8KB)。
- 运行目录:`userData/runs/<runId>/run.json` + `nodes/<nodeId>.json` + `evidence/*.png` + `run.log`。
- UI 只显示相对路径;证据查看器内置。
---
## 8. 运行时实现
### 8.1 浏览器
- 主窗口右侧/独立窗口嵌入 `WebContentsView`,地址栏 + 前进后退 + 刷新 + 关闭。
- 移植 `actions.js`:`navigate/click/type/select/hover/scroll/wait/extract/screenshot/tabs/runJs`;保留统一结果、错误码、后置条件、`actionId` 去重。
- 定位:`testid → aria → text → css → xpath`;导出时 xpath 仅可等价转 css。
- 选择器值 v1 存 `{ kind, value, name? }`;拾取器同时生成厂商式候选项(`commonProps + selector[]` 的简化版),P2 再落完整结构。
- 拾取器:注入 overlay,`mousemove` 高亮、点击捕获并 `preventDefault`,生成候选定位,回填属性面板;元素库(多元素复用)留到 P2,v1 直接写入节点。
- 域名白名单在任何页面动作前校验;默认仅 `127.0.0.1`/`localhost`。
- 高影响动作(click/type/下载/上传)运行前弹一次确认(可设置关闭)。
### 8.2 桌面(PowerShell 桥)
`resources/desktop-bridge.ps1`,stdin/stdout 行分隔 JSON:
| 命令 | 说明 |
|---|---|
| `win.list` / `win.find` / `win.activate` / `win.state` / `win.rect` / `win.close` | EnumWindows / SetForegroundWindow / ShowWindow |
| `uia.find` / `uia.invoke` / `uia.setValue` / `uia.getText` | UIAutomationClient(元素优先) |
| `input.click` / `input.move` / `input.drag` / `input.scroll` | user32 `SendInput` 绝对坐标 |
| `input.type` / `input.key` / `input.hotkey` | `SendInput` Unicode / 虚拟键 |
| `clip.get` / `clip.set` | WinForms Clipboard |
| `screen.capture` | System.Drawing |
| `app.run` | `Start-Process` |
| `sys.env` / `sys.cmd` | 环境变量 / 命令执行(超时+输出截断) |
DPI:
- 桥启动时 `SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2)`。
- 所有坐标使用物理像素;Node 侧用 `screen.getPrimaryDisplay().scaleFactor` 换算。
- 125%/150% 必须跑冒烟用例。
### 8.3 Excel / PDF / Word
- Excel:`exceljs` 读写、sheet 增删改、区域读写、保存/另存;COM 只做"可见打开/关闭"(可选开关)。
- CSV:自研解析(带引号/换行)。
- PDF:`pdfjs-dist` 取文本/页数;`pdf-lib` 合并/新建。
- Word:PowerShell COM(新建/写入/保存/关闭/读取全文);无 Word 时显式报 `office_error`。
### 8.4 图像
- 截屏:Electron `desktopCapturer` 或 PowerShell。
- 模板匹配:优先 Python 3.10 + OpenCV `matchTemplate`(本机已装);未检测到 Python 时用内置 JS 降级(`pngjs` 降采样 + 归一化互相关);两者都不可用时,该组件在目录中禁用并提示 `capability_unavailable`,不影响其他功能。
- OCR:可选,Windows.Media.Ocr;不达标就标注未交付。
> Word 组件依赖本机安装 Word(COM);无 Word 时返回 `office_error`,不影响其他组件。Excel 文件级读写用 `exceljs`,不依赖 Office。
### 8.5 HTTP / 邮件
- HTTP:`fetch`(Node 22 内置),支持方法/头/查询/Body/超时/保存响应。
- 邮件:`nodemailer` SMTP;收件用 `imapflow`(如时间允许);演示用本地 mock SMTP。
---
## 9. 数据存储与安全
| 项 | 方案 |
|---|---|
| 流程 | `%APPDATA%/rpa-studio/flows/<id>.json`,原子写(临时文件+rename) |
| 目录 | `resources/catalog.json`(1016 全量 + 状态 + 字段元数据),启动加载,只读 |
| 运行 | `runs/<runId>/`,保留最近 50 次,自动清理 |
| 设置 | `settings.json`(白名单、超时、证据、缩放) |
| 密钥 | `safeStorage` 加密后存 `secrets.json`,UI 只显示引用名 |
| 日志 | 主进程滚动日志 + 运行日志;单文件 5MB 截断 |
| 安全 | 渲染进程无 Node;IPC 白名单;表达式无 eval;浏览器协议仅 http/https;文件操作不做沙箱(本地工具定位) |
---
## 10. 三天排期(逐半天 + 门禁)
> 今天 10/26 从 10:00 起算;每天按 12~14 小时安排;每半天一个可演示产物。
### Day 1(10/26):工程 + 目录 + 画布 + 属性
| 时间 | 任务 | 产出/门禁 |
|---|---|---|
| 10:00–11:00 | 脚手架(electron-vite/React/TS)、窗口、三栏布局、令牌、路由 | `npm run dev` 出窗口 |
| 11:00–12:30 | 目录管线:解析 1016 → 合并 `componentsNew.json` 的 `vType/validatorRules/tip` → 分组/状态/字段归一 → `catalog.json`;组件库搜索/分组/状态筛选/虚拟滚动 | 左栏 1016 项可搜可拖,灰标正确 |
| 13:30–17:00 | 画布:拖入、节点卡、连线、选择/删除/复制、缩放/平移/MiniMap、撤销重做 | 6 节点可连线 |
| 17:00–19:30 | 容器:IF/计次/遍历 分组节点、子节点拖入、槽位、尺寸自适应 | 节点可拖进 IF 分支 |
| 20:00–22:00 | 属性面板:控件映射、校验、输出变量/类型、失败策略(超时/重试/跳过/延时)、变量面板、表达式插入 | 配置可保存到节点 |
| 22:00–23:30 | 保存/加载、示例流程、空态/错误态、Day1 演示 | 重启后流程还原 |
| **门禁 G1** | 拖 6 节点 + IF + 循环 → 配参数 → 保存 → 重启还原 → 截图留档 | 不通过不进入 Day2 |
### Day 2(10/27):执行内核 + 浏览器 + 桌面
| 时间 | 任务 | 产出/门禁 |
|---|---|---|
| 09:00–12:00 | 表达式解析器、编译器(校验/容器/链/待接入守卫)、运行时(上下文/事件/取消/失败策略:超时/重试/跳过/延时)、Vitest | 直线/IF/循环/重试用例通过 |
| 13:00–16:30 | 浏览器:WebContentsView、CDP 动作移植、拾取器、证据、本地夹具站 | 打开夹具页→点击→输入→取文本 |
| 16:30–19:30 | 桌面桥:PowerShell 常驻、键鼠/窗口/剪贴板/对话框/命令、DPI 换算 | 记事本输入 + 窗口激活 |
| 20:00–23:30 | 能力批量:控制(14)+ 文件(14);运行面板 v1 | 单节点用例批量通过 |
| **门禁 G2** | 一条"浏览器 3 步 + 桌面 2 步"流程可运行,事件流与日志正确 | 不通过则 Day3 砍 PDF/Word |
### Day 3(10/28):Office/网络 + 联调 + 打包
| 时间 | 任务 | 产出/门禁 |
|---|---|---|
| 09:00–11:00 | Excel/CSV(18) | 读写 xlsx、多 sheet、另存 |
| 11:00–12:00 | 数据/文本包(28,表驱动) | 字符串/时间/列表/字典/正则/数学/JSON 用例通过 |
| 13:00–14:30 | PDF/Word(5)、HTTP/SMTP(4) | PDF 合并/取文本、Word 写入、本地 mock 通过 |
| 14:30–15:30 | 图像查找(3,OpenCV) | 模板匹配坐标/置信度达标 |
| 15:30–17:30 | 运行面板完善(变量/证据/重试/停止/待接入定位)、E2E 夹具流程、错误态 | 场景 A 跑通 |
| 17:30–19:00 | 125%/150% 缩放冒烟、边界修复、性能(200 节点流程) | DPI 用例通过 |
| 19:00–21:00 | 打包 portable、干净目录冒烟、自测报告、README/演示脚本 | 双击 exe 可演示 |
| 21:00–22:00 | 缓冲/修复/交付整理 | 交付清单齐 |
| **门禁 G3** | 干净机器解压→运行→场景 A→失败用例→报告 | 最终验收 |
### 砍功能顺序(进度落后时)
1. FTP/数据库(本就不做)
2. Word(保留 PDF)
3. 邮件收件(保留发信)
4. 桌面 UIA(保留坐标键鼠)
5. 循环容器(保留 IF)
6. 图像/OCR
7. 目录组件数(可执行 120 → 80,保浏览器/文件/Excel/数据)
---
## 11. 自测与验收
`scripts/selftest.mjs` 以 Electron 主进程模式启动,逐项执行并写 `docs/自测报告.md`。
| # | 用例 | 期望 |
|---|---|---|
| T01 | 目录加载/分组/搜索/状态 | 1016 项齐全;可执行/待接入计数与注册表一致 |
| T02 | 画布增删/连线/容器 | 数据结构与几何正确 |
| T03 | 保存/加载/撤销 | 重启一致 |
| T04 | 表达式(算术/字符串/时间/成员) | 结果正确,错误定位 |
| T05 | 顺序执行 | 节点按序、变量传递 |
| T06 | IF 真/假分支 | 只执行对应分支 |
| T07 | 计次循环 | 次数/步长正确,可中断 |
| T08 | 遍历列表 | item/index 正确 |
| T09 | 失败策略:停止/重试/跳过/超时/延时 | 行为与配置一致;校验错误不重试 |
| T10 | 取消运行 | 在途动作中止,状态 cancelled |
| T11 | 浏览器导航/点击/输入/取文本 | 真实 DOM 变化,可信输入 |
| T12 | 元素拾取器 | 生成候选定位并可回填 |
| T13 | 浏览器截图证据 | 文件存在、相对路径 |
| T14 | 文件读/写/复制/删除/列表 | 临时目录结果正确 |
| T15 | Excel 写/读/另存 | 单元格值正确 |
| T16 | CSV 读/写 | 引号/换行正确 |
| T17 | PDF 页数/文本/合并 | 结果与样本一致 |
| T18 | Word 写入/保存 | 文档存在且含文本 |
| T19 | 键盘输入记事本 | 文本一致 |
| T20 | 鼠标点击/窗口激活 | 目标窗口前台 |
| T21 | 剪贴板读写 | 往返一致 |
| T22 | 对话框/消息框 | 返回用户选择 |
| T23 | HTTP GET/POST | 本地服务响应正确 |
| T24 | 邮件发送(mock SMTP) | 服务器收到报文 |
| T25 | 图像查找 | 坐标/置信度达标 |
| T26 | 125%/150% 缩放 | 画布不裁切、点击不偏 |
| T27 | 断网启动 | 正常启动与运行离线流程 |
| T28 | 打包产物启动 | 双击可用,无缺失依赖 |
| T29 | 待接入组件拖入并运行 | 节点灰标;运行到该节点报 `capability_not_implemented`,含原因/建议/定位 |
| T30 | 目录元数据类型/校验 | `vType` 推断输出类型;必填校验生效 |
| T31 | 状态筛选/搜索 | 「全部/可执行/待接入」与关键词组合正确 |
报告格式:用例、能力、前置、步骤、期望、实际、结论、证据路径。**未通过项必须列出,不得隐藏。**
---
## 12. 打包与交付
```
release/RPA-Studio-0.1.0-win-x64/
RPA Studio.exe
resources/app.asar
resources/catalog.json
resources/fixture/...
resources/desktop-bridge.ps1
examples/*.json
README.md
version.json
RPA-Studio-0.1.0-win-x64.zip
SHA256SUMS.txt
docs/自测报告.md
```
- 使用 `@electron/packager`(portable 目录),避免 electron-builder 的签名/工具下载风险。
- 打包脚本校验:无 devDependencies、无 node_modules 泄漏、无 `.env`/密钥、资源齐全。
- 干净目录冒烟:复制到 `D:\tmp\rpa-smoke`,断网启动,跑示例流程。
- 版本清单记录源码提交号、构建时间、Electron 版本、可执行组件数。
---
## 13. 风险与应对
| 风险 | 概率 | 影响 | 应对 |
|---|---|---|---|
| 三天做不完 | 高 | 交付缩水 | 按砍功能顺序;G1/G2 门禁强制 |
| 桌面 DPI/焦点 | 中 | 演示失败 | 桥声明 DPI、UIA 优先、坐标兜底、双缩放冒烟 |
| Excel COM 弹窗/慢 | 中 | 卡流程 | 文件级 exceljs;COM 仅可选 |
| 浏览器站点兼容 | 中 | 登录/框架页失败 | 只承诺内置浏览器 + 本地夹具;iframe/影子 DOM 显式报错 |
| 未注册组件误入 | 高 | 运行中断 | 全量目录 + 灰标 + 预运行「N 个待接入」提示 + 运行期 `capability_not_implemented`(含替代建议) |
| npm/Electron 下载 | 低 | 阻塞 | 源已验证;锁版本;Electron 用本地缓存 |
| C 盘空间 | 中 | 构建失败 | 全部在 D 盘;缓存指向 D |
| 需求蔓延 | 高 | 延期 | 本文件为基线,新需求进 P2+ 清单 |
---
## 14. 交付清单
- [ ] `D:\RPA` 源码仓库(git,提交按 G1/G2/G3 打点)
- [ ] `release/RPA-Studio-0.1.0-win-x64.zip`(可双击)
- [ ] `docs/自测报告.md`(31 用例 + 可执行组件清单)
- [ ] `docs/操作手册.md`(10 分钟上手)
- [ ] `examples/` 两条示例流程
- [ ] 本实施方案 + 现状分析
---
## 15. 待确认的默认假设(不回复即按此执行)
1. 产品暂定名 **RPA Studio**,可一处配置改名。
2. 组件库展示 **1016 全量**;未接入项灰标、可拖拽、运行时明确失败;可执行目标 120(保底 90)。
3. 演示场景默认"本地夹具页:填表→抓表格→写 Excel→按金额分支→通知/发信",后续可换成你的系统。
4. OCR 为加分项,不阻塞验收;图像查找优先。
5. 运行期离线;构建期允许联网装依赖。
6. 两份厂商参考资料只做行为参考;不复制代码/二进制/模型/前端资产;三天不做 `.task` 导入/导出。

View File

@ -1,355 +0,0 @@
# 能力接入规划与排期
> 生成时间:2026-10-28 · 校准时间:2026-11-02 · 收口时间:2026-11-02 · 原始目录 1016 项 · 可编排目录 894 项(运行时注册 906 个 id;122 项不接入已分离备份)
## 一、明确不接入清单(永久排除)
组件库显示「不接入」;运行前检查直接提示"该能力已明确不接入"。
### 门户专用(3 个)——绑定特定门户系统账号
- Basic_GetSystemAccount_v2 | 获取平台账号(通用)
- Basic_GetSystemAccount | 获取系统账号(门户专用)
- Basic_GetSystemAccount_v1 | 获取平台账号
### 厂商专属云服务(17 个)——依赖厂商专属数据/场景服务
- DataService_DeleteControllerFiles_v1 | 删除文件
- DataService_ReportException | 上报异常信息
- DataService_ProductionQueue | 将数据存入队列
- DataService_UploadControllerFiles_v1 | 上传文件
- DataService_ServiceNew | 数据服务(松塔)
- DataService_DeleteControllerFiles | 删除控制器文件
- DataService_Service | 数据服务
- DataService_DownloadControllerFiles | 下载控制器文件
- DataService_DownloadControllerFiles_v1 | 下载文件
- DataService_ConsumptionQueue | 从队列获取数据
- DataService_UploadControllerFiles | 上传文件至控制器
- DataService_AiScreenText | 提取屏幕数据(AI)
- DataService_AiScreenDate | 选择日期时间(AI)
- DataService_AiScreenOperate | 操作屏幕(AI)
- Scenario_CreditVerify | 征信报告解析
- Scenario_VatInvoice | 增值税发票验真
- Scenario_PhysicalExamination | 体检报告解析
### SAP 专用(1 个)——依赖SAP客户端
- Element_DoubleClickSap | 双击SAP表格单元格
### 硬件外设(41 个)——依赖手机设备连接
- Mobile_ConnectPhone | 连接手机
- Mobile_MobileElementAarray | 获取手机相似元素数组
- Mobile_Call | 拨打电话
- Mobile_MobileSetText_V1 | 手机键盘输入
- Mobile_ClickButton | 系统导航键
- Mobile_SetInputV1 | 手机输入文本
- Mobile_ConnectPhoneV3 | 连接手机
- Mobile_GetAttribute | 手机元素属性拾取
- Mobile_Exists | 手机元素判断
- Mobile_MobileElementList | 获取手机相似元素列表
- Mobile_FileTransfer | 手机文件传输
- Mobile_GetPhoneInfomation | 获取手机连接详情
- Mobile_ClickLocation | 手机屏幕坐标点击
- Mobile_SaveFilePhone | 保存文件到手机
- Mobile_SetInput | 设置文本|填写输入框
- Mobile_MobileCvClick | 手机智能元素点击
- Mobile_TouchSlideV1 | 触摸滑动
- Mobile_GetPhoneFiles | 从手机中获取文件
- Mobile_OpenMobileApp | 打开手机APP
- Mobile_TouchSlide | 触摸滑动
- Mobile_GetMobileUiTree | 获取手机UI树
- Mobile_GetScreenOrientation | 获取手机屏幕方向
- Mobile_GetElement | 获取手机元素对象
- Mobile_MobileCvSetText | 智能设置文本|填写输入框
- Mobile_ClosePhone | 关闭连接手机
- Mobile_SetInputV2 | 手机输入文本
- Mobile_SetScreenOrientation | 设置手机屏幕方向
- Mobile_ImageClick | 点击手机图像
- Mobile_CloseMobileApp | 关闭手机APP
- Mobile_ElementShot | 手机元素截图
- Mobile_ScreenShot | 手机截屏
- Mobile_GetClipboardText | 获取手机剪切板文本
- Mobile_ConnectPhoneV2 | 连接手机
- Mobile_GetText | 手机元素文本拾取
- Mobile_MobileSetText | 手机键盘输入
- Mobile_MobileElementList_V1 | 获取手机相似元素列表
- Mobile_ElementClick | 手机元素点击
- Mobile_GetChildren | 获取手机子元素
- Mobile_SetClipboardText | 设置手机剪切板文本
- Mobile_ConnectPhoneV1 | 连接手机
- System_GetMobileMessage | 获取手机短信
### 厂商专属云服务(30 个)——依赖厂商训练模型/打码服务
- OCR_RotateVerificationCodeV1 | 旋转图片验证码
- OCR_PuzzleVerificationCode_v1 | 推理拼图验证码
- OCR_ClickVerificationCodeV2 | 点选验证码
- OCR_SourceCommonEnglishNumbersCodeV2 | 普通验证码
- OCR_SimpleSliderVerificationCode | 滑块验证码(滑动条式)
- OCR_SourceMergeCardOCR | 识别卡证文字
- OCR_SliderVerificationCodeV5 | 滑动拼图验证码
- OCR_SliderVerificationCodeV4 | 滑动拼图验证码
- OCR_SourceMergeOtherOCR | 识别其他场景文字
- OCR_SliderCode | 滑块验证码
- OCR_SourceMergeGeneralOCR | 通用文字识别
- OCR_RotateVerificationCodeV2 | 旋转图片验证码
- OCR_RotateVerificationCode | 滑块验证码(旋转式)
- OCR_ClickVerificationCodeV1 | 点选验证码
- OCR_PuzzleVerificationCode | 推理拼图验证码
- OCR_SimpleSliderVerificationCodeV2 | 滑动条验证码
- OCR_SourceCommonEnglishNumbersCode | 普通验证码
- OCR_SourceMergeCarOCR | 识别汽车场景文字
- OCR_SliderVerificationCodeV2 | 滑块验证码(拼图样式)
- OCR_SliderVerificationCodeV1 | 滑块验证码(拼图样式)
- OCR_SliderVerificationCode | 滑块验证码(拼图样式)
- OCR_SourceMergeEducationOCR | 识别教育场景文字
- OCR_SliderVerificationCodeV3 | 滑动拼图验证码
- OCR_ClickVerificationCode | 点选验证码
- OCR_SourceCommonEnglishNumbersCodeV1 | 普通验证码
- OCR_PuzzleVerificationCode_v2 | 推理拼图验证码
- OCR_SourceMergePaperOCR | 识别票据文字
- OCR_SimpleSliderVerificationCodeV1 | 滑动条验证码
- OCR_SourceMergeGeneralOCR_V1 | 识别通用文字
- OCR_SliderVerificationCodePosition | 获取拼图滑块位置
### 厂商专属云服务(4 个)——依赖厂商离线OCR模型文件
- OcrOffline_ClickOcrText_V1 | 鼠标点击OCR文本
- OcrOffline_MoveToOcrText | 鼠标移动到OCR文本上
- OcrOffline_ClickOcrText | 鼠标点击OCR文本
- OcrOffline_MoveToOcrText_V1 | 鼠标移动到OCR文本上
### 厂商专属云服务(6 个)——依赖百度云OCR账号与密钥
- OCR_BaiDu_CarOCR | 汽车场景类OCR
- OCR_BaiDu_OtherOCR | 其他场景类OCR
- OCR_BaiDu_EducationOCR | 教育场景类OCR
- OCR_BaiDu_GeneralOCR | 通用OCR
- OCR_BaiDu_CardOCR | 卡片类OCR
- OCR_BaiDu_PaperOCR | 票据类OCR
### 硬件外设(6 个)——依赖USB通道外设
- peripherals_UsbCom | 开/关USB通道
- peripherals_CloseNetworkUsbCom | 关闭NetworkUSB通道
- peripherals_CloseUsbCom | 关闭USB通道
- peripherals_NetworkUsbCom | 开/关NetworkUSB通道
- peripherals_OpenNetworkUsbCom | 开启NetworkUSB通道
- peripherals_OpenUsbCom | 开启USB通道
### 硬件外设(1 个)——依赖银行安全控件键盘
- SafeControlInput_KeyInput | 安全控件文本输入
### 硬件外设(4 个)——依赖机械臂设备
- System_ResetMechanicalArm | 复位机械臂
- System_MechanicalArmClick | 机械臂点击
- System_ConnectingMechanicalArm | 连接机械臂
- System_CloseMechanicalArm | 断开机械臂
### 硬件外设(2 个)——依赖U盾设备
- System_ConnectingUshield | 连接U盾
- System_CloseUshield | 断开U盾
### 语义已被覆盖(1 个)——下载能力已覆盖下载场景
- WebBrowser_DownloadDialog | 处理下载对话框
**小计:116 个 id**(2026-10-30 校准:原 128 项中 12 项已接入,移至下方清单)
### 已接入(原排除项,2026-10-30 校准移出)
通过设置页配置的 OpenAI 兼容 AI 服务接入(`src/main/capabilities/ai-caps.ts`):
- DataService_AIGenTxt | AI生成文本
- DataService_LargeModel | 大模型
- DataService_ZBrain | 自定义AI模型
- DataService_AgentCall | 调用智能体
- DataService_ChatBot | chatbot对话机器人
- DataService_KeyWord | 关键词抽取
- DataService_Correct | 文本纠错
- DataService_Review | 文本审核
- Scenario_TextComparison | 文档比对
通过本地离线 OCR 模型接入(`src/main/capabilities/ocr-caps.ts`):
- OcrOffline_GetOcrTextNew | 图像OCR识别
- OcrOffline_GetOcrTable | 获取表格数据(OCR)
- OcrOffline_GetOcrTextPosition | 图像OCR识别(含坐标)
## 二、暂缓池
### 已完成(超出原排期,2026-11-02 校准)
- [P1] 人机交互对话框 17/17:输入/文件/消息/自定义表单对话框(`src/main/capabilities/human-dialog.ts`)
- [P2] Windows 系统 12/12:凭据/远程桌面/屏保/输入法(`src/main/capabilities/windows-system.ts`)
- [P2] 图表数据读取 2/2:Element_GetLineBarChartData、Element_GetPieChartData(`src/main/capabilities/web-batch2.ts`)
- [P2] Excel 高阶 38/38:单元格格式、区域选择、行列显隐、尾部空白单元格、列标识、表格样式、Load/Close(`src/main/capabilities/excel-common.ts`);组合、图片、加密、移动、合并、透视表、宏、PDF 导出、Interop、切片器、循环(`src/main/capabilities/excel-com.ts`)
- [P3] 待评估池移出 20 项:FTP 10 项(`src/main/capabilities/ftp.ts`)、NLP 9 项(AI 服务 `src/main/capabilities/ai-caps.ts`)、File_OpenFile
- 原排除清单移出 12 项:见第一节「已接入」
以下为剩余暂缓项:
### 2026-11-02 完成批次(64 个)
- 桌面 Ground 补全 13/13:12 个 `*Ground` 变体 + `Element_DesktopChildren`(`src/main/capabilities/desktop-uia.ts`)
- Word 选区/光标/表格 COM 11/11(`src/main/capabilities/word-extended.ts`)
- PDF 加密/提取图片/转图片/表格 4/4(`src/main/capabilities/pdf.ts`)
- 邮件查找/回复/Outlook 读取 9/9(`src/main/capabilities/message.ts`)
- 键盘模拟人工开关 2/2 与 `Basic_Dodecryption` 1/1(`src/main/capabilities/desktop.ts`、`basic-encryption.ts`)
- Excel COM 专属 24/24(`src/main/capabilities/excel-com.ts`)
### [P3] 待评估(80 个)
- child_modules_v1 | 调用子流程
- concurrent_child_process_v1 | 并发调用子流程
- Database_BatchSQLExecute | 批量执行SQL语句
- Database_DBConnect | 连接数据库
- Database_SingleSQLQuery | 执行单SQL查询
- Database_BatchInsertExecute | SQL批量插入数据
- Database_AllSQLQuery_V1 | 执行全SQL查询
- Database_CloseDBConnect | 关闭连接
- Database_SQLExecute | 执行SQL语句
- Database_AllSQLQuery | 执行全SQL查询
- File_UploadBusinessResults_v1 | 上报办理结果
- File_StartBusiness | 上报办理业务
- File_UploadBusinessResults | 上报办理结果(门户专用)
- File_Collectupload | 上报采集数据
- File_UploadFolder | 结果打包上传
- FileTypeConversion_Office2pdf | OFFICE文档转PDF
- FileTypeConversion_Pdf2image2 | PDF转图片
- GoogleTable_GetAllData | 获取列信息(Google Sheets)
- GoogleTable_GetAllSheetsName | 获取Sheet名(Google Sheets)
- GoogleTable_WriteToSheetRegionColumns | 写入列(Google Sheets)
- GoogleTable_WriteToSheetRegionRows | 写入行(Google Sheets)
- GoogleTable_WriteToSheetCell | 写入单元格(Google Sheets)
- GoogleTable_WriteToSheetCellFormula | 写入单元格公式(Google Sheets)
- GoogleTable_GetSheetRowCount | 获取行数(Google Sheets)
- GoogleTable_DownloadSheets | 导出Sheet(Google Sheets)
- GoogleTable_CopyPasteSheet | 复制粘贴(Google Sheets)
- GoogleTable_GetColumnsByTableTitle | 按表头读取列(Google Sheets)
- GoogleTable_ChangeSheet | 激活Sheet(Google Sheets)
- GoogleTable_RenameSheet | 重命名Sheet(Google Sheets)
- GoogleTable_WriteToSheetRegionregion | 写入区域(Google Sheets)
- Image_Move_V2 | 鼠标移动到图像上
- Image_Base64Decode | 图片base64编码转二进制
- Image_Base64Encode | 图片转base64编码
- Image_Wait_V1 | 等待图像
- Image_Exists | 判断图像
- Image_Location | 图像位置
- Image_Location_V1 | 获取图像位置
- Image_Exists_V1 | 判断图像
- Image_CropImg | 截取图像
- Image_RelativePosition | 对比图像相对位置
- Image_Wait | 等待图像
- Image_Move | 移动到图像
- Image_Move_V1 | 移动到图像
- Mark_Mark | 图片打标
- NLP_XunFei_MachineTranslation | 机器翻译
- NLP_XunFei_FeelingAnalysis | 第三方情感分析模型
- NLP_XunFei_KeywordExtraction | 关键字提取
- OCR_MergeCardOCR | 卡证文字识别
- OCR_CalculationCode | 算术验证码
- OCR_IdCardV2 | OCR身份证
- OCR_BusinessCardV2 | OCR名片
- OCR_VehicleInvoiceV2 | OCR机动车发票
- OCR_MergeOtherOCR | 其他场景文字识别
- OCR_BusinessLicenseV3 | OCR营业执照(自研)
- OCR_PassportV2 | OCR护照
- OCR_MergeGeneralOCR | 通用文字识别
- OCR_KeyValueIdentification | 识别键值对结构
- OCR_AmountIdentification | 识别价格金额
- OCR_GeneralTextV2 | OCR高精度文本
- OCR_VatInvoiceV2 | OCR增值税发票
- OCR_DrivingLicenseV2 | OCR行驶证
- OCR_AccountPageV2 | OCR户口页
- OCR_PlateLicenseV2 | OCR车牌
- OCR_BankCardV2 | OCR银行卡
- OCR_BankCardV3 | OCR银行卡(自研)
- OCR_IdCardV3 | OCR身份证(自研)
- OCR_VatInvoiceV3 | OCR增值税发票(自研)
- OCR_MergePaperOCR | 票据文字识别
- OCR_DriverLicenseV2 | OCR驾驶证
- OCR_RealEstateCertV2 | OCR不动产权证
- OCR_TrainTicketV2 | OCR火车票
- OCR_TaxiInvoiceV2 | OCR出租车发票
- OCR_OfficialSealV2 | OCR公章
- OCR_MergeCarOCR | 汽车场景文字识别
- OCR_MergeEducationOCR | 教育场景文字识别
- OCR_HouseCertV2 | OCR房产证
- OCR_CommonEnglishNumbersCode | 普通验证码
- OCR_BusinessLicenseV2 | OCR营业执照
- OCR_VinV2 | OCR车辆vin码
- Element_exception_monitor_v2 | 异常监控
### [P2] 视觉定位与 OCR 读屏(5 个)
- logic_control_if_ai_vision | 判断屏幕图像(AI)
- CV_GetText | 获取文本
- CV_SetText | 文本输入
- CV_MouseHover | 移动到元素上
- CV_MouseClick | 元素点击
### [P2] 邮件收件:剩余连接/列表/操作/Exchange(19 个;查找、回复、Outlook 读取 9 个已完成)
- Mail_UnmarkSeen | 标为未读
- Mail_DoReplyMail_v1 | 回复邮件(Outlook)
- Mail_RecvConnect | 连接邮箱
- Mail_RecvMailGet | 取第几封邮件
- Mail_ExOperateMail | 操作邮件
- Mail_RecvGetList | 邮件筛选
- Mail_DoMoveMail | 移动邮件
- Mail_UnionConnect | 连接邮箱
- Mail_ExConnect | 连接Exchange邮箱
- Mail_RecvClose | 关闭连接
- Mail_DoSendMail | 发送邮件
- Mail_RecvCount | 取收件箱邮件数
- Mail_ExSearchMail | 查找邮件
- Mail_ExSendMail | 发送邮件
- Mail_OperateMail | 操作邮件
- Mail_MarkSeen | 标为已读
- Mail_DoMoveMail_v1 | 操作邮件(Outlook)
- Mail_DoReplyMail | 回复邮件
- Mail_DoSendMail_v1 | 发送邮件(Outlook)
## 三、接入排期总览
| 阶段 | 内容 | 状态 |
|---|---|---|
| P0 | 网页核心 170、桌面 Ground 核心 5、网页批二 15 id、网页批三 16 id | 已完成 2026-10-28 |
| P1 | 人机交互对话框 17、Time 家族 24 | 已完成 2026-10-30 校准 |
| P1 | 文件上传验证 | 下一迭代 |
| P2 | 最后一波 98:子流程、数据库、转换、Google Sheets、图像、CV、NLP、OCR、邮件剩余 | 已完成 2026-11-02 |
| 备份 | 门户上报、图片打标、移动硬件、厂商云服务、SAP 等 122 项 | 已分离 `resources/excluded.json` |
| 永久排除 | 明确不接入清单 116 项(第一节) | 不排期 |
## 四、落地机制
- 本文档为能力接入的唯一规划来源;新增能力须先在此登记阶段
- 2026-11-02 校准:数字以运行时注册表为准(808 个 id,目录可执行 796 / 1016);2026-10-30 校准后原排除/暂缓中已实现的 78 项已移入「已接入 / 已完成」
- 本批次新增依赖:`@cantoo/pdf-lib`(PDF 加密)、`@napi-rs/canvas`(PDF 渲染)、`imapflow`/`mailparser`(IMAP 收件)、`sm-crypto`(SM4 密文变量)
- 不接入能力备份:[04-未接入名单](04-未接入名单.md)
- 下一步:将第一节清单固化为 resources/excluded.json,组件库显示「不接入」灰标
## 六、桌面拾取与定位——原厂实现解读与我们的增强项(2026-10-30)
### 原厂实在智能的实现(源码解读)
- 拾取(ui_selector_window.py):全屏半透明覆盖窗 + 高亮窗贴**窗格级**元素 + 左键确认;输出 uiSelector = { window: [{class,type,name} 链], shot: 自动截图 }——**拾取时自动截取目标窗口图片存档**
- 执行定位(Element/py/scope/native.py):路径链逐层走 UIA 树——每层匹配 type/class/name **通配符** + 同名兄弟 **index 序号**;窗口锚用标题通配(EnumWindows)
- 桌面点击执行:**窗口定位 + 图像/坐标模式**(原厂 Image/CV 能力家族庞大的原因)
### 我们当前方案对比
- 定位:UIA 属性选择器(name/id 精确匹配,单层)——精度高于原厂通配符,但**缺路径链/通配/序号 fallback**
- 拾取:v2 两阶段(悬停/微调)+ PageUp/PageDown 层级切换 + 最小化应用——已实现
### 登记的增强项(后续迭代)
1. 链式查找:拾取器已输出元素链(layers),执行端支持逐层匹配(原厂模式)——解决 name 重复场景
2. name 通配符匹配与同名序号 index
3. 拾取确认时自动截取目标窗口图片存档(调试回看)
4. WebView2 深层 DOM 控件的 UIA 暴露深度调优

View File

@ -1,186 +0,0 @@
# 04-外接浏览器支持改动方案(Edge / Chrome)
- **编写时间**:2026-10-30
- **编写人**:刘爱家
- **关联文档**:01-现状分析与可行性 / 02-实施方案 / 03-能力接入规划与排期
- **目标**:让「打开网页」及全部网页自动化组件支持在客户本机的 Edge / Chrome 浏览器中执行,不再强制使用内置运行浏览器;内置浏览器保留为默认兜底。
---
## 一、现状分析(代码级)
### 1.1 现有架构
```
网页组件(web.ts / web-batch2.ts / web-crawl.ts,169 项目录项)
│ BrowserLike 接口(engine/types.ts,33 个方法)
▼
BrowserSession(browser/session.ts,1056 行,单例)
│ this.cdp(method, params)
▼
win.webContents.debugger(Electron 内置窗口「RPA 运行浏览器」)
```
- **会话模型**:`BrowserSession.create()` 单例复用一个 `BrowserWindow`,全局变量 `__rpaBrowserWindow` 持有;单窗口单 Tab 模型(`tab_unavailable` 错误即来自此)。
- **CDP 使用现状**:全部经 `webContents.debugger`(协议版本 1.3),使用的域:
- `DOM.getDocument / DOM.querySelector / DOM.setFileInputFiles`
- `Input.dispatchMouseEvent / Input.dispatchKeyEvent / Input.insertText`
- `Runtime.evaluate`(仅 runJs 一处)
- `Page.enable / Page.captureScreenshot / Page.handleJavaScriptDialog`
- `Network.enable / Network.getResponseBody`(网页监听)
- **全部是标准 CDP 域,Edge / Chrome(同为 Chromium)原生支持,命令层零改动。**
- **目录字段已预留**:`WebBrowser_Create / V2 / V3` 均有 `type`(浏览器类型,默认 chrome)、`isRemote`(是否远程)、`ipAddress`、`port`(默认 61619)、`arguments`(携带参数)、`openNewTab` 字段——**但实现不读取**,无论填什么一律打开内置窗口。
### 1.2 关键约束
| 约束 | 说明 |
|---|---|
| Electron 31 主进程 = Node 20.x | 无稳定原生 WebSocket 客户端,连接外部调试端口需引入 `ws` |
| 用户已开着的浏览器 | 未带调试端口启动的实例无法连接(Chromium 安全限制),必须带参重启 |
| 内置窗口独有 API | `webContents.debugger` 仅 Electron 可用;`Page.captureScreenshot` 外部浏览器同样可用(证据截图无障碍) |
| 元素拾取器 | 现拾取链路绑定内置窗口,外部浏览器需改走 CDP DOM 快照 |
---
## 二、总体设计
**核心思路:传输层抽象,一次替换,上层不动。**
```
BrowserLike(接口不变)
▼
BrowserSession(cdp() 委托给 transport,其余逻辑不变)
▼
CdpTransport 接口(新增)
├── EmbeddedTransport —— 现有 webContents.debugger 封装(内置浏览器,默认)
└── ExternalTransport —— WebSocket 直连外部浏览器调试端口(新增,依赖 ws)
```
- `BrowserLike` 接口 33 个方法、上层 169 个网页组件**全部零改动**。
- 内置浏览器保留:`type` 未填或填内置类型时走原路径,行为与现在完全一致(回归风险隔离)。
### 浏览器类型枚举约定
| type 值 | 行为 |
|---|---|
| `chrome` | 探测本机 Chrome 路径 → 带调试端口启动 → CDP 接管 |
| `edge` | 探测本机 Edge 路径 → 同上 |
| `chrome_remote` / `edge_remote`(或 isRemote=1) | 不启动进程,直接连 `ipAddress:port` 上已监听调试端口的实例 |
| `zexplorer` / 缺省 | 内置运行浏览器(现状行为,兜底) |
---
## 三、分阶段实施
### 阶段一:传输层抽象 + 外部浏览器连接(核心,约 2~3 天)
**新增 `src/main/browser/transport.ts`**
```ts
export interface CdpTransport {
send<T>(method: string, params?: Record<string, unknown>): Promise<T>;
on(event: string, handler: (params: unknown) => void): void; // Page.loadEventFired 等
close(): Promise<void>;
kind: "embedded" | "external";
}
```
- `EmbeddedTransport`:包装现有 `webContents.debugger` 的 `send/on`(`session.ts` 现有 `cdp()` 逻辑原样迁入)。
- `ExternalTransport`:基于 `ws` 包,连接 `http://ip:port` 的调试端点:
1. `GET /json/list` 枚举页面级 target,取 `webSocketDebuggerUrl`(或 `PUT /json/new?url=` 开新 Tab)
2. 建立WebSocket,消息按 CDP 格式封包(`{id, method, params}` / 响应 `{id, result|error}` / 事件 `{method, params}`),promise map 按 id 配对
3. 断线检测(ws close 事件)→ 抛 `browser_disconnected`,带「请带调试端口重启浏览器」提示
**新增 `src/main/browser/external.ts`(外部浏览器进程管理)**
- 路径探测(按优先级):
- Chrome:注册表 `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe` → `C:\Program Files\Google\Chrome\Application\chrome.exe` → `C:\Program Files (x86)\...`
- Edge:`HKLM\...\App Paths\msedge.exe` → `C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe`(Win10/11 必装,探测成功率 100%)
- 启动:`child_process.spawn(browserPath, [--remote-debugging-port=<port>, --user-data-dir=<独立目录>, url, ...args])`
- **必须带独立 `--user-data-dir`**:否则端口参数会被用户已开的实例吞掉(连不上且无报错的头号坑)
- 端口管理:默认取 `port` 字段(61619),被占用时自增重试;启动后轮询 `GET /json/version`(超时 15s)确认就绪
- 进程生命周期:记录 child process 引用;流程 `WebBrowser_Close` 时可选关闭(目录可加「执行后是否关闭浏览器」字段)
**改造 `src/main/browser/session.ts`**
- `BrowserSession` 构造函数改为接收 `CdpTransport`;`cdp()` 方法改为一行委托 `this.transport.send()`
- 单例键从 `__rpaBrowserWindow` 扩展为「按 (type, user-data/port) 维度」的会话缓存,支持同流程混合使用内置 + 外部
- `close()`:embedded 关窗口(现状逻辑);external 断开 ws,按配置决定是否杀浏览器进程
### 阶段二:「打开网页」组件参数生效(约 1 天)
**改造 `src/main/capabilities/browser.ts` 的 `WebBrowser_Create / V2 / V3 / CreateBackground`:**
```
type + isRemote + ipAddress + port + arguments ──→ BrowserSessionFactory
├─ type ∈ {chrome, edge} 且 isRemote=0 → external.ts 启动并连接
├─ isRemote=1 → 直连 ipAddress:port
└─ 其余 → 内置浏览器(现状)
```
- `waitingload`(等待页面加载完成):external 模式下监听 `Page.loadEventFired` 实现,语义与内置一致
- `arguments`(携带参数):透传给浏览器启动命令行
- 日志明确输出实际使用的浏览器(如「已使用 Edge (127.0.0.1:9222) 打开 https://...」),便于客户排查
### 阶段三:多标签页管理(约 2~3 天)
现状单 Tab 模型在外部浏览器不成立,需引入 `Target` 域:
- `Target.getTargets` / `Target.attachToTarget`(flatten 模式)/ `Target.closeTarget`
- 落地组件:`WebBrowser_GetTAB_V1`(按标题/URL 匹配 Tab,type 字段终于生效)、`WebBrowser_Switch`(切换当前操作的 Tab)、`CreateV3` 的 `openNewTab`
- 实现方式:ExternalTransport 按 targetId 维护多个 ws 会话,BrowserSession 增加 `switchTab(targetId)`;内置浏览器对应到多个 `webContents`
### 阶段四:拾取器与高级组件适配(约 2~3 天)
- 元素拾取:外部浏览器场景拾取请求走 CDP `DOMSnapshot.captureSnapshot` / `DOM.getDocument` 生成候选元素树(内置窗口继续走现有拾取链路)
- 逐组件验证(阶段一~三完成后自然可用,重点回归):`WebBrowser_GetCookieNew`、`WebBrowser_StartWeblistening / GetWeblistening`(Network 域,外部同样可用)、`WebBrowser_BatchDataCrawlNew`(browserType 字段接阶段二同一工厂)、`WebBrowser_SaveAs`(`DOM.setFileInputFiles` + `Page.handleJavaScriptDialog`)
- `Element_*` 系列 100+ 组件无需改动,靠接口层自动生效
---
## 四、依赖与风险
### 新增依赖
| 包 | 大小 | 用途 | 风险 |
|---|---|---|---|
| `ws` | ~50KB 纯 JS | 连接外部浏览器调试端口(Node 20 无原生 WebSocket) | 无原生编译,供应链风险低 |
**客户机器零安装**:Edge/Chrome 原生支持 CDP,不装任何扩展插件。
### 风险与对策
| 风险 | 对策 |
|---|---|
| 用户已开浏览器实例吞掉端口参数(连不上且无提示) | 必带独立 `--user-data-dir` 启动;文档注明该限制 |
| 调试端口与客户其他软件冲突 | 端口自增探测 + `/json/version` 就绪轮询 |
| 外部浏览器被客户手动关闭,流程中途断连 | ws close 事件 → 明确报错「浏览器已关闭」,错误路由可接重开逻辑 |
| 页面弹窗阻塞 CDP | `Page.handleJavaScriptDialog` 与内置行为一致,现有组件已覆盖 |
| 混用内置/外部时上下文串流 | 会话缓存按 (type, port) 隔离;`RunContext.browser` 生命周期不变 |
| URL 安全 | 沿用现有 `assertUrl`(仅 http/https)白名单逻辑,外部连接的 ip:port 另做私网地址校验 |
---
## 五、工作量与排期估算
| 阶段 | 内容 | 预估 |
|---|---|---|
| 一 | 传输层抽象 + 外部连接 + 进程管理 | 2~3 天 |
| 二 | 打开网页组件参数生效 | 1 天 |
| 三 | 多标签 Target 域 | 2~3 天 |
| 四 | 拾取器适配 + 高级组件回归 | 2~3 天 |
| 测试 | 与同事网页自动化测试联动,双浏览器(Edge/Chrome)× 关键链路 | 1~2 天 |
| **合计** | | **约 1.5~2 周** |
阶段一+二完成后即可向客户交付「用 Edge/Chrome 打开并执行基础网页自动化」;阶段三、四为完整体验补齐,可穿插进行。
---
## 六、测试计划(验收标准)
1. **连接**:Edge / Chrome 各验证——全新启动、已装非默认路径(注册表探测)、isRemote 远程连接、端口占用自增
2. **基础链路**:打开网页 → 点击 → 输入 → 取文本 → 截图 → 关闭,双浏览器全通过
3. **多标签**:开 3 个 Tab → GetTAB 按标题匹配 → 切换 → 各 Tab 独立操作 → closeTarget
4. **兜底**:type 缺省/填 zexplorer 时行为与现版本完全一致(内置浏览器回归)
5. **异常**:浏览器中途被手动关、URL 非法、调试端口不通、页面弹窗
6. **数据采集/监听**:BatchDataCrawlNew、StartWeblistening 在外部浏览器下与内置结果一致

View File

@ -1,141 +0,0 @@
# 不接入能力备份
> 快照时间:2026-11-02 · 该清单已从可编排目录分离,仅作能力溯源备份。
## 汇总
| 状态 | 数量 |
|---|---:|
| 原始目录总数 | 1016 |
| 当前可编排目录 | 894 |
| 不接入备份 | 122 |
| 运行时注册 id | 906 |
| ID | 名称 | 模块 | 原厂函数 |
|---|---|---|---|
| `Basic_GetSystemAccount_v2` | 获取平台账号(通用) | app | `Basic.GetSystemAccount_v2` |
| `Basic_GetSystemAccount` | 获取系统账号(门户专用) | app | `Basic.GetSystemAccount` |
| `Basic_GetSystemAccount_v1` | 获取平台账号 | app | `Basic.GetSystemAccount_v1` |
| `DataService_DeleteControllerFiles_v1` | 删除文件 | message | `DataService.DeleteControllerFiles_v1` |
| `DataService_ReportException` | 上报异常信息 | message | `DataService.ReportException` |
| `DataService_ProductionQueue` | 将数据存入队列 | message | `DataService.ProductionQueue` |
| `DataService_UploadControllerFiles_v1` | 上传文件 | message | `DataService.UploadControllerFiles_v1` |
| `DataService_ServiceNew` | 数据服务(松塔) | message | `DataService.ServiceNew` |
| `DataService_DeleteControllerFiles` | 删除控制器文件 | message | `DataService.DeleteControllerFiles` |
| `DataService_Service` | 数据服务 | message | `DataService.Service` |
| `DataService_DownloadControllerFiles` | 下载控制器文件 | message | `DataService.DownloadControllerFiles` |
| `DataService_DownloadControllerFiles_v1` | 下载文件 | message | `DataService.DownloadControllerFiles_v1` |
| `DataService_ConsumptionQueue` | 从队列获取数据 | message | `DataService.ConsumptionQueue` |
| `DataService_UploadControllerFiles` | 上传文件至控制器 | message | `DataService.UploadControllerFiles` |
| `DataService_AiScreenText` | 提取屏幕数据(AI) | message | `DataService.AiScreenText` |
| `DataService_AiScreenDate` | 选择日期时间(AI) | message | `DataService.AiScreenDate` |
| `DataService_AiScreenOperate` | 操作屏幕(AI) | message | `DataService.AiScreenOperate` |
| `Element_DoubleClickSap` | 双击SAP表格单元格 | web | `Element.DoubleClickSap` |
| `File_UploadBusinessResults_v1` | 上报办理结果 | file | `File.UploadBusinessResults_v1` |
| `File_StartBusiness` | 上报办理业务 | file | `File.StartBusiness` |
| `File_UploadBusinessResults` | 上报办理结果(门户专用) | file | `File.UploadBusinessResults` |
| `File_Collectupload` | 上报采集数据 | file | `File.Collectupload` |
| `File_UploadFolder` | 结果打包上传 | file | `File.UploadFolder` |
| `Mark_Mark` | 图片打标 | vision | `Mark.Mark` |
| `Mobile_ConnectPhone` | 连接手机 | mobile | `Mobile.ConnectPhone` |
| `Mobile_MobileElementAarray` | 获取手机相似元素数组 | mobile | `Mobile.MobileElementAarray` |
| `Mobile_Call` | 拨打电话 | mobile | `Mobile.Call` |
| `Mobile_MobileSetText_V1` | 手机键盘输入 | mobile | `Mobile.MobileSetText_V1` |
| `Mobile_ClickButton` | 系统导航键 | mobile | `Mobile.ClickButton` |
| `Mobile_SetInputV1` | 手机输入文本 | mobile | `Mobile.SetInputV1` |
| `Mobile_ConnectPhoneV3` | 连接手机 | mobile | `Mobile.ConnectPhoneV3` |
| `Mobile_GetAttribute` | 手机元素属性拾取 | mobile | `Mobile.GetAttribute` |
| `Mobile_Exists` | 手机元素判断 | mobile | `Mobile.Exists` |
| `Mobile_MobileElementList` | 获取手机相似元素列表 | mobile | `Mobile.MobileElementList` |
| `Mobile_FileTransfer` | 手机文件传输 | mobile | `Mobile.FileTransfer` |
| `Mobile_GetPhoneInfomation` | 获取手机连接详情 | mobile | `Mobile.GetPhoneInfomation` |
| `Mobile_ClickLocation` | 手机屏幕坐标点击 | mobile | `Mobile.ClickLocation` |
| `Mobile_SaveFilePhone` | 保存文件到手机 | mobile | `Mobile.SaveFilePhone` |
| `Mobile_SetInput` | 设置文本|填写输入框 | mobile | `Mobile.SetInput` |
| `Mobile_MobileCvClick` | 手机智能元素点击 | mobile | `Mobile.MobileCvClick` |
| `Mobile_TouchSlideV1` | 触摸滑动 | mobile | `Mobile.TouchSlideV1` |
| `Mobile_GetPhoneFiles` | 从手机中获取文件 | mobile | `Mobile.GetPhoneFiles` |
| `Mobile_OpenMobileApp` | 打开手机APP | mobile | `Mobile.OpenMobileApp` |
| `Mobile_TouchSlide` | 触摸滑动 | mobile | `Mobile.TouchSlide` |
| `Mobile_GetMobileUiTree` | 获取手机UI树 | mobile | `Mobile.GetMobileUiTree` |
| `Mobile_GetScreenOrientation` | 获取手机屏幕方向 | mobile | `Mobile.GetScreenOrientation` |
| `Mobile_GetElement` | 获取手机元素对象 | mobile | `Mobile.GetElement` |
| `Mobile_MobileCvSetText` | 智能设置文本|填写输入框 | mobile | `Mobile.MobileCvSetText` |
| `Mobile_ClosePhone` | 关闭连接手机 | mobile | `Mobile.ClosePhone` |
| `Mobile_SetInputV2` | 手机输入文本 | mobile | `Mobile.SetInputV2` |
| `Mobile_SetScreenOrientation` | 设置手机屏幕方向 | mobile | `Mobile.SetScreenOrientation` |
| `Mobile_ImageClick` | 点击手机图像 | mobile | `Mobile.ImageClick` |
| `Mobile_CloseMobileApp` | 关闭手机APP | mobile | `Mobile.CloseMobileApp` |
| `Mobile_ElementShot` | 手机元素截图 | mobile | `Mobile.ElementShot` |
| `Mobile_ScreenShot` | 手机截屏 | mobile | `Mobile.ScreenShot` |
| `Mobile_GetClipboardText` | 获取手机剪切板文本 | mobile | `Mobile.GetClipboardText` |
| `Mobile_ConnectPhoneV2` | 连接手机 | mobile | `Mobile.ConnectPhoneV2` |
| `Mobile_GetText` | 手机元素文本拾取 | mobile | `Mobile.GetText` |
| `Mobile_MobileSetText` | 手机键盘输入 | mobile | `Mobile.MobileSetText` |
| `Mobile_MobileElementList_V1` | 获取手机相似元素列表 | mobile | `Mobile.MobileElementList_V1` |
| `Mobile_ElementClick` | 手机元素点击 | mobile | `Mobile.ElementClick` |
| `Mobile_GetChildren` | 获取手机子元素 | mobile | `Mobile.GetChildren` |
| `Mobile_SetClipboardText` | 设置手机剪切板文本 | mobile | `Mobile.SetClipboardText` |
| `Mobile_ConnectPhoneV1` | 连接手机 | mobile | `Mobile.ConnectPhoneV1` |
| `OCR_RotateVerificationCodeV1` | 旋转图片验证码 | vision | `OCR.RotateVerificationCodeV1` |
| `OCR_PuzzleVerificationCode_v1` | 推理拼图验证码 | vision | `OCR.PuzzleVerificationCode_v1` |
| `OCR_ClickVerificationCodeV2` | 点选验证码 | vision | `OCR.ClickVerificationCodeV2` |
| `OCR_SourceCommonEnglishNumbersCodeV2` | 普通验证码 | vision | `OCR.SourceCommonEnglishNumbersCodeV2` |
| `OCR_SimpleSliderVerificationCode` | 滑块验证码(滑动条式) | vision | `OCR.SimpleSliderVerificationCode` |
| `OCR_SourceMergeCardOCR` | 识别卡证文字 | vision | `OCR.SourceMergeCardOCR` |
| `OCR_SliderVerificationCodeV5` | 滑动拼图验证码 | vision | `OCR.SliderVerificationCodeV5` |
| `OCR_SliderVerificationCodeV4` | 滑动拼图验证码 | vision | `OCR.SliderVerificationCodeV4` |
| `OCR_SourceMergeOtherOCR` | 识别其他场景文字 | vision | `OCR.SourceMergeOtherOCR` |
| `OCR_SliderCode` | 滑块验证码 | vision | `OCR.SliderCode` |
| `OCR_SourceMergeGeneralOCR` | 通用文字识别 | vision | `OCR.SourceMergeGeneralOCR` |
| `OCR_RotateVerificationCodeV2` | 旋转图片验证码 | vision | `OCR.RotateVerificationCodeV2` |
| `OCR_RotateVerificationCode` | 滑块验证码(旋转式) | vision | `OCR.RotateVerificationCode` |
| `OCR_ClickVerificationCodeV1` | 点选验证码 | vision | `OCR.ClickVerificationCodeV1` |
| `OCR_PuzzleVerificationCode` | 推理拼图验证码 | vision | `OCR.PuzzleVerificationCode` |
| `OCR_SimpleSliderVerificationCodeV2` | 滑动条验证码 | vision | `OCR.SimpleSliderVerificationCodeV2` |
| `OCR_SourceCommonEnglishNumbersCode` | 普通验证码 | vision | `OCR.SourceCommonEnglishNumbersCode` |
| `OCR_SourceMergeCarOCR` | 识别汽车场景文字 | vision | `OCR.SourceMergeCarOCR` |
| `OCR_SliderVerificationCodeV2` | 滑块验证码(拼图样式) | vision | `OCR.SliderVerificationCodeV2` |
| `OCR_SliderVerificationCodeV1` | 滑块验证码(拼图样式) | vision | `OCR.SliderVerificationCodeV1` |
| `OCR_SliderVerificationCode` | 滑块验证码(拼图样式) | vision | `OCR.SliderVerificationCode` |
| `OCR_SourceMergeEducationOCR` | 识别教育场景文字 | vision | `OCR.SourceMergeEducationOCR` |
| `OCR_SliderVerificationCodeV3` | 滑动拼图验证码 | vision | `OCR.SliderVerificationCodeV3` |
| `OCR_ClickVerificationCode` | 点选验证码 | vision | `OCR.ClickVerificationCode` |
| `OCR_SourceCommonEnglishNumbersCodeV1` | 普通验证码 | vision | `OCR.SourceCommonEnglishNumbersCodeV1` |
| `OCR_PuzzleVerificationCode_v2` | 推理拼图验证码 | vision | `OCR.PuzzleVerificationCode_v2` |
| `OCR_SourceMergePaperOCR` | 识别票据文字 | vision | `OCR.SourceMergePaperOCR` |
| `OCR_SimpleSliderVerificationCodeV1` | 滑动条验证码 | vision | `OCR.SimpleSliderVerificationCodeV1` |
| `OCR_SourceMergeGeneralOCR_V1` | 识别通用文字 | vision | `OCR.SourceMergeGeneralOCR_V1` |
| `OCR_SliderVerificationCodePosition` | 获取拼图滑块位置 | vision | `OCR.SliderVerificationCodePosition` |
| `OcrOffline_ClickOcrText_V1` | 鼠标点击OCR文本 | vision | `OcrOffline.ClickOcrText_V1` |
| `OcrOffline_MoveToOcrText` | 鼠标移动到OCR文本上 | vision | `OcrOffline.MoveToOcrText` |
| `OcrOffline_ClickOcrText` | 鼠标点击OCR文本 | vision | `OcrOffline.ClickOcrText` |
| `OcrOffline_MoveToOcrText_V1` | 鼠标移动到OCR文本上 | vision | `OcrOffline.MoveToOcrText_V1` |
| `OCR_BaiDu_CarOCR` | 汽车场景类OCR | vision | `OCR_BaiDu.CarOCR` |
| `OCR_BaiDu_OtherOCR` | 其他场景类OCR | vision | `OCR_BaiDu.OtherOCR` |
| `OCR_BaiDu_EducationOCR` | 教育场景类OCR | vision | `OCR_BaiDu.EducationOCR` |
| `OCR_BaiDu_GeneralOCR` | 通用OCR | vision | `OCR_BaiDu.GeneralOCR` |
| `OCR_BaiDu_CardOCR` | 卡片类OCR | vision | `OCR_BaiDu.CardOCR` |
| `OCR_BaiDu_PaperOCR` | 票据类OCR | vision | `OCR_BaiDu.PaperOCR` |
| `peripherals_UsbCom` | 开/关USB通道 | mobile | `peripherals.UsbCom` |
| `peripherals_CloseNetworkUsbCom` | 关闭NetworkUSB通道 | mobile | `peripherals.CloseNetworkUsbCom` |
| `peripherals_CloseUsbCom` | 关闭USB通道 | mobile | `peripherals.CloseUsbCom` |
| `peripherals_NetworkUsbCom` | 开/关NetworkUSB通道 | mobile | `peripherals.NetworkUsbCom` |
| `peripherals_OpenNetworkUsbCom` | 开启NetworkUSB通道 | mobile | `peripherals.OpenNetworkUsbCom` |
| `peripherals_OpenUsbCom` | 开启USB通道 | mobile | `peripherals.OpenUsbCom` |
| `SafeControlInput_KeyInput` | 安全控件文本输入 | desktop | `SafeControlInput.KeyInput` |
| `Scenario_CreditVerify` | 征信报告解析 | message | `Scenario.CreditVerify` |
| `Scenario_VatInvoice` | 增值税发票验真 | message | `Scenario.VatInvoice` |
| `Scenario_PhysicalExamination` | 体检报告解析 | message | `Scenario.PhysicalExamination` |
| `System_ResetMechanicalArm` | 复位机械臂 | app | `System.ResetMechanicalArm` |
| `System_MechanicalArmClick` | 机械臂点击 | app | `System.MechanicalArmClick` |
| `System_ConnectingUshield` | 连接U盾 | app | `System.ConnectingUshield` |
| `System_ConnectingMechanicalArm` | 连接机械臂 | app | `System.ConnectingMechanicalArm` |
| `System_CloseMechanicalArm` | 断开机械臂 | app | `System.CloseMechanicalArm` |
| `System_CloseUshield` | 断开U盾 | app | `System.CloseUshield` |
| `System_GetMobileMessage` | 获取手机短信 | app | `System.GetMobileMessage` |
| `WebBrowser_DownloadDialog` | 处理下载对话框 | web | `WebBrowser.DownloadDialog` |
## 恢复方式
如后续需要恢复某项,先实现对应 handler,再从 `resources/excluded.json` 的 `ids` 与 `components` 中移除,并重新运行 `npm run package`。

View File

@ -1,925 +0,0 @@
# 重复能力去留清单
> 快照时间:2026-11-02 · 保留 658 个主能力,隐藏 236 个重复旧版本 / 别名;执行器保留,旧流程不受影响。
## 去留规则
1. 先按能力名称聚合。
2. 再按目标域拆分:CV、桌面 Ground、网页元素、Excel、数据库、邮件协议等不互相吞并。
3. 同域内保留最新主版本;带 `Vn` 的优先级高于基础旧 ID,`New` 仅在没有更高版本时保留。
4. 重复 ID 标记 `duplicateOf` 后从组件库隐藏,但运行器和旧流程兼容保留。
## 汇总
| 状态 | 数量 |
|---|---:|
| 保留主能力 | 658 |
| 隐藏重复能力 | 236 |
| 原可编排数量 | 894 |
| 清理后组件库数量 | 658 |
## 保留主能力
| ID | 名称 |
|---|---|
| `Trigger.Manual` | 人工发起 |
| `Trigger.Schedule` | 定时触发 |
| `Trigger.FileArrival` | 文件到达触发 |
| `logic_control_if_else` | IF 条件分支 |
| `logic_control_var` | 设置变量 |
| `Basic_SetVariable_V1` | 设置变量 |
| `logic_control_count_for` | 计次循环 |
| `logic_control_while` | 条件循环 |
| `infinite_loop` | 无限循环 |
| `logic_control_next_for` | 继续下次循环 |
| `logic_control_break_for` | 跳出循环 |
| `logic_control_try_catch` | 异常捕获 |
| `logic_control_stop` | 退出流程 |
| `logic_control_stop_return` | 返回并退出流程 |
| `child_modules_v1` | 调用子流程 |
| `concurrent_child_process_v1` | 并发调用子流程 |
| `logic_control_array_for_v3` | 遍历列表 |
| `logic_control_dictionary_for_v1` | 遍历字典 |
| `Element_exception_monitor_v2` | 异常监控 |
| `logic_control_if_ai_vision` | 判断屏幕图像(AI) |
| `App_Run` | 启动应用程序 |
| `App_GetPid` | 获取一个匹配PID |
| `App_GetPids` | 获取全部匹配PID |
| `App_KillV1` | 关闭应用程序 |
| `Array_Reshape2Dim` | 转化数组维度 |
| `Basic_IsDictionary` | 判断数据是否为字典 |
| `Basic_StringToNum` | 将文本转换为数字 |
| `Basic_Delay_V2` | 等待 |
| `Basic_ConvertInt` | 数值转为整数类型 |
| `Basic_RandomInt` | 生成随机数 |
| `Basic_IsNumber` | 判断数据是否为数值 |
| `Basic_ConvertBool` | 数据转为布尔类型 |
| `Basic_Type` | 获取数据类型 |
| `Basic_Dodecryption` | 设置密文变量 |
| `Basic_IsNull` | 判断数据是否为空 |
| `Basic_DebugOutput` | 调试输出 |
| `Basic_BusinessOrderCount` | 业务办理计数 |
| `Basic_ConvertNumber` | 数据转为数值类型 |
| `Basic_GetProcessInfo` | 获取流程信息 |
| `Basic_ConvertString` | 将数字转换为文本 |
| `Basic_Delay` | 流程延迟执行 |
| `Basic_IsArray` | 判断数据是否为数组 |
| `Basic_Clone` | 复制数据 |
| `Basic_JudgeDataType` | 判断数据类型 |
| `Basic_DataTypeConversion` | 转换数据类型 |
| `Bivartable_CreateDfV1` | 创建数据表格 |
| `Bivartable_GetMaxValue` | 获取最大值 |
| `Bivartable_DelDfColRow` | 删除行/列 |
| `Bivartable_ReadDfData_V1` | 读取数据 |
| `Bivartable_GetMinValue` | 获取最小值 |
| `Bivartable_HandleDfMissValue` | 缺失值处理 |
| `Bivartable_ColumnOperationDf` | 两列运算 |
| `Bivartable_SortDf` | 排序数据表格 |
| `Bivartable_ReadTable` | 读取数据表内容 |
| `Bivartable_DoCreate` | 创建二维表 |
| `Bivartable_WriteDfData` | 写入数据 |
| `Bivartable_FilterDf` | 筛选数据表格 |
| `Bivartable_SummaryCalculationDf` | 按列汇总计算 |
| `Bivartable_DoSort` | 排序 |
| `Bivartable_DoFilter` | 表格过滤 |
| `Bivartable_InsertDfData` | 插入数据 |
| `Bivartable_ReadDfIndexColumns` | 读取行名/列名 |
| `Bivartable_ReadDfShape` | 读取行数/列数 |
| `Bivartable_DfWriteToFile` | 写入到文件 |
| `Bivartable_DoSave` | 保存二维表至Excel |
| `Bivartable_PivotDf` | 生成数据透视表 |
| `Bivartable_GetSumValue` | 获取统计总和 |
| `Bivartable_GetAvgValue` | 获取平均值 |
| `Bivartable_SpliceColData` | 拼接两列数据 |
| `Bivartable_Dtype` | 列数据类型转换 |
| `Bivartable_ModifyDfName` | 修改行名/列名 |
| `Bivartable_ModifyColName` | 修改列名 |
| `Bivartable_DeleteDuplicateRow` | 删除重复行 |
| `Bivartable_MergeDf` | 合并数据表格 |
| `Bivartable_DoOpenExcel` | 基于Excel创建二维表 |
| `Bivartable_DoMerge` | 合并二维表 |
| `Bivartable_WriteTable` | 数据表写入内容 |
| `Bivartable_DoJoint` | 拼接列 |
| `Bivartable_DoCal` | 列计算 |
| `Clipboard_SetImage` | 设置剪切板图片 |
| `Clipboard_SetTextV1` | 将文本添加到剪切板 |
| `Clipboard_GetText` | 取剪切板文本 |
| `Clipboard_SaveImageV1` | 获取剪切板文件 |
| `Clipboard_GetTextV1` | 获取剪切板文本 |
| `Clipboard_ClearClipboard` | 清空剪切板 |
| `Clipboard_SetImageV1` | 将文件添加到剪切板 |
| `Clipboard_SetText` | 设置剪切板文本 |
| `Clipboard_SaveImage` | 取剪切板图片 |
| `Collection_Create` | 创建集合 |
| `Collection_HasItem` | 判断集合元素存在 |
| `Collection_Count` | 取集合大小 |
| `Collection_Remove` | 删除集合元素 |
| `Collection_Add` | 添加集合元素 |
| `CSV_ReadNew` | 读取内容 |
| `CSV_WriteNew` | 写入内容 |
| `CustomConfig_GetConfig` | 自定义配置 |
| `CV_GetText` | 获取文本 |
| `CV_SetText` | 文本输入 |
| `Element_SetValue_V1` | 文本输入 |
| `Element_SetValueGround` | 文本输入 |
| `CV_MouseHover` | 移动到元素上 |
| `CV_MouseClick` | 元素点击 |
| `Element_Click_V1` | 元素点击 |
| `Element_ClickGround` | 元素点击 |
| `Database_BatchSQLExecute` | 批量执行SQL语句 |
| `Database_DBConnect` | 连接数据库 |
| `Database_SingleSQLQuery` | 执行单SQL查询 |
| `Database_BatchInsertExecute` | SQL批量插入数据 |
| `Database_AllSQLQuery_V1` | 执行全SQL查询 |
| `Database_CloseDBConnect` | 关闭连接 |
| `Mail_RecvClose` | 关闭连接 |
| `Database_SQLExecute` | 执行SQL语句 |
| `Databook_WriteContent` | 写入内容至数据表 |
| `Databook_DeleteColumn` | 删除数据表格列 |
| `Databook_Export` | 导出数据表格 |
| `Databook_GetColumnInfo` | 获取列信息 |
| `Databook_ReadContent` | 读取数据表格内容 |
| `Databook_Clear` | 清空数据表格 |
| `Databook_SetColumnInfo` | 设置列描述 |
| `Databook_Import` | 导入数据表格 |
| `Databook_GetRowAndColCount` | 读取数据表格总行列数 |
| `Databook_DeleteRow` | 删除数据表格行 |
| `DataService_Correct` | 文本纠错 |
| `DataService_AgentCall` | 调用智能体 |
| `DataService_AIGenTxt` | AI生成文本 |
| `DataService_WorkingDay` | 判断是否工作日 |
| `DataService_Diff` | 文本比对 |
| `DataService_LargeModel` | 大模型 |
| `DataService_Review` | 文本审核 |
| `DataService_ChatBot` | chatbot对话机器人 |
| `DataService_KeyWord` | 关键词抽取 |
| `DataService_ZBrain` | 自定义AI模型 |
| `Dialog_SaveFile` | 保存文件对话框 |
| `Dialog_CsInputBox` | 输入对话框 |
| `Dialog_GetConfig` | 输入输出对话框 |
| `Dialog_GetConfigV7` | 人机交互表单 |
| `Dialog_Notice_V2` | 消息通知 |
| `Dialog_OpenFile` | 打开文件对话框 |
| `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_OpenFiles` | 打开文件对话框(多选) |
| `Dialog_CsMsgBox` | 消息确认对话框 |
| `Dict_Create` | 创建字典 |
| `Dict_Delete` | 删除键值对 |
| `Dict_Items` | 获取字典所有键值 |
| `Dict_Values` | 获取字典所有值 |
| `Dict_Clear` | 清空字典 |
| `Dict_GetItem` | 获取字典值 |
| `Dict_Keys` | 获取字典所有键 |
| `Dict_Update` | 合并字典 |
| `Dict_SetItem` | 设置字典值 |
| `Element_SetValuePassword_V1` | 输入密码框 |
| `Element_Region2_v3` | 获取元素坐标 |
| `Element_RegionGround` | 获取元素坐标 |
| `Element_SelectOperation_v3` | 下拉框操作 |
| `Element_Sibling_V2` | 获取兄弟元素 |
| `Element_Children_V2` | 获取子元素 |
| `Element_ChildrenGround` | 获取子元素 |
| `Element_ElementAarray` | 获取相似元素数组 |
| `Element_Exists` | 元素判断 |
| `Element_ExistsGround` | 元素判断 |
| `Element_GetCheckGround` | 获取勾选框状态值 |
| `Element_GetCheck` | 获取勾选框状态值 |
| `Element_SetAttribute_v2` | 设置网页元素属性 |
| `Element_SetAttributeGround` | 设置网页元素属性 |
| `Element_WaitElement_v5` | 等待元素 |
| `Element_WaitElementGround` | 等待元素 |
| `Element_ScrollIntoView_v2` | 滚动页面 |
| `Element_ScrollIntoViewGround` | 滚动页面 |
| `Element_MouseTo_V4` | 鼠标移动到元素上 |
| `Element_MouseToGround` | 鼠标移动到元素上 |
| `Element_Click_V6` | 点击界面元素 |
| `Element_SetCheckGround` | 设置勾选框状态 |
| `Element_SetCheck` | 设置勾选框状态 |
| `Element_Parent_V2` | 获取父元素 |
| `Element_SetValue_V3` | 输入文本框 |
| `Element_StopAutoClosePopup` | 停止智能弹窗侦测 |
| `Element_GetButtonEnable_v4` | 判断元素是否可点击 |
| `Element_SetValue_V6` | 输入文本 |
| `Element_GetText_V5` | 获取界面元素文本 |
| `Element_GetValue` | 获取元素文本 |
| `Element_GetValueGround` | 获取元素文本 |
| `Element_SimilarElementList_V2` | 获取相似元素列表 |
| `Element_Shot_V2` | 元素截图 |
| `Element_ShotGround` | 元素截图 |
| `Element_CheckBoxOperation_v3` | 勾选框操作 |
| `Element_GetLineBarChartData` | 获取折线图/柱状图数据 |
| `Element_GetAttribute_v3` | 获取网页元素属性 |
| `Element_GetAttributeGround` | 获取网页元素属性 |
| `Element_GetSelect` | 获取下拉框选项值 |
| `Element_GetSelectGround` | 获取下拉框选项值 |
| `Element_SetValuePassword_V6` | 输入密码 |
| `Element_GetText_V1` | 获取区域文字 |
| `Element_GetElementNew` | 获取元素对象 |
| `Element_GetElementInfo_V1` | 获取元素信息 |
| `Element_ExistsV1` | 判断元素存在 |
| `Element_GetPieChartData` | 获取饼状图数据 |
| `Element_GetTableData` | 获取SAP表格数据 |
| `Element_StartAutoClosePopup` | 启动智能弹窗侦测 |
| `Element_SetSelectGround` | 设置下拉框选择项 |
| `Element_SetSelect` | 设置下拉框选择项 |
| `Element_GetHtml` | 获取网页元素源码 |
| `Element_GetButtonEnableGround` | 元素是否可点击 |
| `Element_FileUploader` | 上传文件 |
| `WebBrowser_Upload` | 上传文件 |
| `Excel_WriteColumnNew` | 写入列 |
| `Excel_Cs_SetCellFormat_V3` | 设置单元格格式 |
| `Excel_Cs_ReadContentV4` | 读取Excel内容 |
| `Excel_Cs_AutoFillV2` | 自动填充 |
| `Excel_NewExcel` | 新建excel |
| `Excel_Cs_CopyV2` | 复制Sheet |
| `Excel_WriteRangeNew` | 写入区域 |
| `Excel_Cs_DeleteColumn` | 删除列 |
| `Excel_Cs_OpenV4` | 打开Excel |
| `Excel_PasteRange` | 粘贴区域 |
| `Excel_OpenExcelNew` | 打开excel |
| `Excel_CopyRange` | 复制区域 |
| `Excel_Cs_ClearRangeV4` | 清除区域 |
| `Excel_Cs_InsertColumnV3` | 插入列 |
| `Excel_Cs_ReadRowCount` | 读取行数 |
| `Excel_Cs_ReadColumnCount` | 读取列数 |
| `Excel_Cs_Group` | 组合行列 |
| `Excel_ReadCellNew` | 读取单元格 |
| `Excel_WriteCellNew` | 写入单元格 |
| `Excel_Cs_SetRangeFontV2` | 设置区域字体颜色 |
| `Excel_Cs_DeletePictureV2` | 删除图片 |
| `Excel_Cs_NewV5` | 新建Excel |
| `Excel_GetColDataNew` | 读取列 |
| `Excel_GetSheetsName` | 获取sheet名 |
| `Excel_Cs_GetEmptyCell` | 获取第一个空白单元格 |
| `Excel_Cs_GetEditRange` | 获取已编辑区域 |
| `Excel_Cs_InsertRowV3` | 插入行 |
| `Excel_Cs_RefreshPivotTable` | 刷新Excel数据透视表 |
| `Excel_Cs_SetHeightV1` | 设置行高 |
| `Excel_Cs_Encrypt` | 加密Excel |
| `Excel_Cs_Move` | 移动Sheet |
| `Excel_Cs_DeleteDuplicates` | 删除重复 |
| `Excel_Cs_DeleteRangeV3` | 删除区域 |
| `Excel_Cs_ExcelFilter` | Excel数据筛选 |
| `Excel_Cs_GetPath` | 获取Excel路径 |
| `Excel_Cs_DeleteRow` | 删除行 |
| `Excel_Cs_Active` | 激活Sheet |
| `Excel_ReadRangeNew` | 读取区域 |
| `Excel_Cs_ConvertColumn` | 列名转换 |
| `Excel_Cs_InsertPictureV2` | 插入图片 |
| `Excel_Cs_Insert` | 插入Sheet |
| `Excel_Cs_Merge` | 合并Sheet |
| `Excel_Cs_ReadCellFormula` | 获取单元格公式 |
| `Excel_Save` | 保存excel |
| `Excel_Cs_InsertPivotView` | 新建透视图 |
| `Excel_Cs_Filter` | 数据筛选 |
| `Excel_SetCellFontColor` | 单元格字体颜色 |
| `Excel_Cs_CopyRangeV3` | 复制表格内容 |
| `Excel_Cs_SetWidthV1` | 设置列宽 |
| `Excel_SetRangeColor` | 区域颜色 |
| `Excel_Cs_Interop` | Excel动态调用 |
| `Excel_Cs_InsertPivotTable` | 新建Excel数据透视表 |
| `Excel_Cs_RunMacroV2` | 执行宏 |
| `Excel_Cs_FindV2` | Excel查找/替换 |
| `Excel_Cs_SaveAs` | 另存为Excel |
| `Excel_Loop` | excel内容循环 |
| `Excel_Cs_GetLastEmptyCell` | 获取尾部第一个空白单元格 |
| `Excel_WriteRowNew` | 写入行 |
| `Excel_GetRowDataNew` | 读取行 |
| `Excel_SetRangeFontColor` | 区域字体颜色 |
| `Excel_GetColumnId` | 获取Excel列标识 |
| `Excel_Cs_SetCellFont` | 设置单元格字体颜色 |
| `Excel_Cs_Delete` | 删除Sheet |
| `Excel_ActiveSheet` | 激活sheet |
| `Excel_Cs_Export` | 导出Excel为PDF |
| `Excel_Cs_MergeCellsV2` | 合并单元格 |
| `Excel_Cs_ShowTableStyle` | 设置Excel表格样式 |
| `Excel_Cs_CloseV2` | 关闭Excel |
| `Excel_Cs_SelectRangeV3` | 选中区域 |
| `Excel_Cs_ClearExcelFilter` | 清除Excel数据筛选 |
| `Excel_Close` | 关闭excel |
| `Excel_Cs_Rename` | 重命名Sheet |
| `Excel_Cs_SetCellBackground` | 设置单元格背景颜色 |
| `Excel_Cs_Save` | 保存Excel |
| `Excel_SetCellColor` | 单元格背景颜色 |
| `Excel_Cs_GetName` | 获取Sheet名 |
| `Excel_Cs_PastRangeV2` | 粘贴表格内容 |
| `Excel_Cs_ColumnSplitV2` | Excel分列 |
| `Excel_Cs_SortV1` | Excel数据排序 |
| `Excel_Cs_ExcelAddin` | 数据处理 |
| `Excel_Cs_ReadCellBackground` | 获取单元格背景色 |
| `Excel_RenameSheet` | 重命名sheet |
| `Excel_Cs_FilterPivotTable` | 筛选Excel数据透视表 |
| `Excel_SaveAs` | 另存excel |
| `Excel_Cs_Hide` | 隐藏/显示行列 |
| `Excel_Cs_SetSlicer` | Excel切片器 |
| `Excel_Cs_WriteContent` | 写入Excel内容 |
| `Excel_Cs_EnableExcelFilter` | 取消Excel数据筛选 |
| `Excel_Cs_SetRangeBackgroundV2` | 设置区域背景颜色 |
| `Excel_Cs_LoadV2` | 获取已打开Excel |
| `Excel_Cs_RefreshAll` | 刷新Excel数据 |
| `Excel_CopySheet` | 复制sheet |
| `Excel_CreateSheet` | 创建sheet |
| `Excel_ExportRangeAsPicture` | 导出区域为图片 |
| `Excel_Cs_Search` | 查找/替换数据 |
| `Excel_DeleteSheet` | 删除sheet |
| `ExceptionUtil_Throws` | 抛出异常 |
| `File_Rename` | 重命名文件 |
| `File_Delete` | 删除文件 |
| `File_DecompressFile` | 解压文件 |
| `File_GetFileList_V1` | 获取文件列表 |
| `File_File_Move` | 移动文件 |
| `File_MoveFolder` | 移动文件夹 |
| `File_Write_V1` | 写入文件 |
| `File_PathSize` | 取文件大小 |
| `File_MakeDir` | 创建文件夹 |
| `File_Copy_V1` | 复制文件/文件夹 |
| `File_GetFileInfo` | 获取文件信息 |
| `File_GetFolderList_V1` | 获取文件夹列表 |
| `File_ReadXml` | 读取xml文件 |
| `File_OpenFile` | 打开文件夹 |
| `File_Read` | 读取文件 |
| `File_Rename_V1` | 重命名文件/文件夹 |
| `File_CreateFile` | 新建文件 |
| `File_DeleteFile` | 删除文件/文件夹 |
| `File_PathSizeNew` | 获取文件大小 |
| `File_CreateFolder` | 生成上传路径 |
| `File_Copy` | 复制文件 |
| `File_CompressFile` | 压缩文件 |
| `File_FileExists` | 判断文件存在 |
| `File_PathExists` | 判断路径 |
| `File_Move` | 移动文件/文件夹 |
| `File_EmptyFolder` | 清空文件夹 |
| `File_WaitFile` | 等待文件 |
| `FileTypeConversion_Office2pdf` | OFFICE文档转PDF |
| `FileTypeConversion_Pdf2image2` | PDF转图片 |
| `FTP_Download` | 下载文件/文件夹(FTP) |
| `FTP_Rename` | 重命名文件/文件夹(FTP) |
| `FTP_Delete` | 删除文件/文件夹(FTP) |
| `FTP_Disconnect` | 断开FTP连接 |
| `FTP_Cwd` | 切换工作目录 |
| `FTP_GetFileList` | 获取文件/文件夹(FTP) |
| `FTP_CreateDir` | 创建文件夹(FTP) |
| `FTP_Exist` | 判断文件存在(FTP) |
| `FTP_Upload` | 上传文件/文件夹(FTP) |
| `FTP_Connect` | 建立FTP连接 |
| `GoogleTable_GetAllData` | 获取列信息(Google Sheets) |
| `GoogleTable_GetAllSheetsName` | 获取Sheet名(Google Sheets) |
| `GoogleTable_WriteToSheetRegionColumns` | 写入列(Google Sheets) |
| `GoogleTable_WriteToSheetRegionRows` | 写入行(Google Sheets) |
| `GoogleTable_WriteToSheetCell` | 写入单元格(Google Sheets) |
| `GoogleTable_WriteToSheetCellFormula` | 写入单元格公式(Google Sheets) |
| `GoogleTable_GetSheetRowCount` | 获取行数(Google Sheets) |
| `GoogleTable_DownloadSheets` | 导出Sheet(Google Sheets) |
| `GoogleTable_CopyPasteSheet` | 复制粘贴(Google Sheets) |
| `GoogleTable_GetColumnsByTableTitle` | 按表头读取列(Google Sheets) |
| `GoogleTable_ChangeSheet` | 激活Sheet(Google Sheets) |
| `GoogleTable_RenameSheet` | 重命名Sheet(Google Sheets) |
| `GoogleTable_WriteToSheetRegionregion` | 写入区域(Google Sheets) |
| `Http_Post_V3` | POST请求 |
| `Http_Get_V2` | GET请求 |
| `Http_Http_Request` | HTTP 请求 |
| `Image_Click_V2` | 点击图像 |
| `Image_Move_V2` | 鼠标移动到图像上 |
| `Image_Screenshot` | 屏幕截图 |
| `Image_Base64Decode` | 图片base64编码转二进制 |
| `Image_Find_V1` | 查找图像 |
| `Image_Base64Encode` | 图片转base64编码 |
| `Image_Wait_V1` | 等待图像 |
| `Image_Exists_V1` | 判断图像 |
| `Image_Location` | 图像位置 |
| `Image_Location_V1` | 获取图像位置 |
| `Image_CropImg` | 截取图像 |
| `Image_Click_V3` | 鼠标点击图像 |
| `Image_RelativePosition` | 对比图像相对位置 |
| `Image_Move_V1` | 移动到图像 |
| `Ini_DeleteKey` | 删除INI文件键 |
| `Ini_Write` | 写入INI文件键值 |
| `Ini_ListSection` | 枚举INI文件小节 |
| `Ini_DeleteSection` | 删除INI文件小节 |
| `Ini_ListKey` | 枚举INI文件键 |
| `Ini_Read` | 读取INI文件键值 |
| `JSON_Stringify` | 将JSON转换为文本 |
| `JSON_Parse` | 将文本转换为JSON |
| `Keyboard_PressNew` | 键盘模拟按键 |
| `Keyboard_DriveSend_V2` | 键盘驱动输入 |
| `Keyboard_Press` | 键盘按键 |
| `Keyboard_Send_V1` | 键盘输入 |
| `Keyboard_EnableManualOperation` | 开启模拟人工操作 |
| `Keyboard_DisableManualOperation` | 关闭模拟人工操作 |
| `List_DeleteList` | 删除列表项 |
| `List_GetIndex` | 获取列表项的位置 |
| `List_ListMerge` | 列表合并 |
| `List_ClearList` | 清空列表 |
| `List_ListLength` | 获取列表长度 |
| `List_ListSorted` | 列表排序 |
| `List_ListDuplication` | 列表去重 |
| `List_InsertList` | 新增列表项 |
| `List_ListIntersection` | 获取两列表的重复项 |
| `List_CreateList` | 创建列表 |
| `List_GetListItem` | 获取指定位置项的数据 |
| `List_SetListItem` | 修改指定位置项的数据 |
| `Log_Error` | 打印错误日志 |
| `Log_Info` | 打印普通日志 |
| `Mail_UnmarkSeen` | 标为未读 |
| `Mail_DoReplyMail_v1` | 回复邮件(Outlook) |
| `Mail_RecvConnect` | 连接邮箱 |
| `Mail_RecvMailGet` | 取第几封邮件 |
| `Mail_ExOperateMail` | 操作邮件 |
| `Mail_OperateMail` | 操作邮件 |
| `Mail_RecvGetList` | 邮件筛选 |
| `Mail_ReplyMail_v2` | 回复邮件 |
| `Mail_DoReplyMail` | 回复邮件 |
| `Mail_Send_V1` | 发送邮件 |
| `Mail_DoSendMail` | 发送邮件 |
| `Mail_ExSendMail` | 发送邮件 |
| `Mail_FindMail_v1` | 查找邮件 |
| `Mail_ExSearchMail` | 查找邮件 |
| `Mail_DoMoveMail` | 移动邮件 |
| `Mail_ExConnect` | 连接Exchange邮箱 |
| `Mail_RecvCount` | 取收件箱邮件数 |
| `Mail_DoGetMailMessage_v2` | 获取邮件(Outlook) |
| `Mail_DoGetMailMessage_v1` | 查找邮件(Outlook) |
| `Mail_FindMail_v2` | 获取邮件 |
| `Mail_DoGetMailMessage` | 查看邮件 |
| `Mail_MarkSeen` | 标为已读 |
| `Mail_DoMoveMail_v1` | 操作邮件(Outlook) |
| `Mail_DoSendMail_v1` | 发送邮件(Outlook) |
| `Math_Cos` | 余弦值 |
| `Math_Tan` | 正切值 |
| `Math_Scale` | 进制转换 |
| `Math_Round` | 四舍五入 |
| `Math_Pow` | x的y次幂 |
| `Math_Int` | 取整 |
| `Math_Sqrt` | 平方根 |
| `Math_Oct` | 转8进制 |
| `Math_Sin` | 正弦值 |
| `Math_Abs` | 绝对值 |
| `Math_Hex` | 转16进制 |
| `Math_Exp` | e的n次幂 |
| `Math_Sgn` | 取正负符号 |
| `MessageNotification_Feishu_Message` | 发送消息至飞书 |
| `MessageNotification_DingTalk_Message` | 发送消息至钉钉 |
| `MessageNotification_WXWork_Message` | 发送消息至企业微信 |
| `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_DragToNew` | 鼠标拖拽 |
| `Mouse_RollTo` | 鼠标滚动 |
| `Mouse_MoveToV2` | 移动鼠标 |
| `NLP_BatchAnalyseProductReview` | 商品评论分析(批量) |
| `NLP_KeyWordsAnalysis` | 关键词 |
| `NLP_EmotionalAnalysis` | 情感分析 |
| `NLP_SplitText` | 实体抽取 |
| `NLP_AnalyseProductReview` | 商品评论分析 |
| `NLP_SemanticSimilarity` | 语义相似度 |
| `NLP_SplitWords` | 分词 |
| `NLP_PerspectiveAnalysis` | 观点分析 |
| `NLP_XunFei_NLPBaseServer` | 第三方NLP模型 |
| `NLP_XunFei_MachineTranslation` | 机器翻译 |
| `NLP_XunFei_FeelingAnalysis` | 第三方情感分析模型 |
| `NLP_XunFei_KeywordExtraction` | 关键字提取 |
| `OCR_MergeCardOCR` | 卡证文字识别 |
| `OCR_CalculationCode` | 算术验证码 |
| `OCR_IdCardV2` | OCR身份证 |
| `OCR_BusinessCardV2` | OCR名片 |
| `OCR_VehicleInvoiceV2` | OCR机动车发票 |
| `OCR_MergeOtherOCR` | 其他场景文字识别 |
| `OCR_BusinessLicenseV3` | OCR营业执照(自研) |
| `OCR_PassportV2` | OCR护照 |
| `OCR_MergeGeneralOCR` | 通用文字识别 |
| `OCR_KeyValueIdentification` | 识别键值对结构 |
| `OCR_AmountIdentification` | 识别价格金额 |
| `OCR_GeneralTextV2` | OCR高精度文本 |
| `OCR_VatInvoiceV2` | OCR增值税发票 |
| `OCR_DrivingLicenseV2` | OCR行驶证 |
| `OCR_AccountPageV2` | OCR户口页 |
| `OCR_PlateLicenseV2` | OCR车牌 |
| `OCR_BankCardV2` | OCR银行卡 |
| `OCR_BankCardV3` | OCR银行卡(自研) |
| `OCR_IdCardV3` | OCR身份证(自研) |
| `OCR_VatInvoiceV3` | OCR增值税发票(自研) |
| `OCR_MergePaperOCR` | 票据文字识别 |
| `OCR_DriverLicenseV2` | OCR驾驶证 |
| `OCR_RealEstateCertV2` | OCR不动产权证 |
| `OCR_TrainTicketV2` | OCR火车票 |
| `OCR_TaxiInvoiceV2` | OCR出租车发票 |
| `OCR_OfficialSealV2` | OCR公章 |
| `OCR_MergeCarOCR` | 汽车场景文字识别 |
| `OCR_MergeEducationOCR` | 教育场景文字识别 |
| `OCR_HouseCertV2` | OCR房产证 |
| `OCR_CommonEnglishNumbersCode` | 普通验证码 |
| `OCR_BusinessLicenseV2` | OCR营业执照 |
| `OCR_VinV2` | OCR车辆vin码 |
| `OcrOffline_GetOcrTextNew` | 图像OCR识别 |
| `OcrOffline_GetOcrTable` | 获取表格数据(OCR) |
| `OcrOffline_GetOcrTextPosition` | 图像OCR识别(含坐标) |
| `PDF_GetPdfInformation` | 获取 PDF 文档信息 |
| `PDF_SetPdfWaterMarkV1` | PDF 加水印 |
| `PDF_PdfEncryption` | 加密 PDF 文档 |
| `PDF_GetPdfImg` | 提取 PDF 图片 |
| `PDF_GetPdfPageNum` | 获取 PDF 总页数 |
| `PDF_ConcatPdfV1` | 合并 PDF 文档 |
| `PDF_GetPdfTable` | 读取 PDF 表格 |
| `PDF_PdfToImg` | PDF 转图片 |
| `PDF_CreateNewPdf` | 生成新的 PDF 文档 |
| `PDF_GetPdfText` | 读取 PDF 文本 |
| `Regex_Test` | 匹配判断 |
| `Regex_FindAll` | 全部查找 |
| `Regex_FindNew_1` | 文本内容提取(指定) |
| `Regex_FindStr` | 文本内容提取 |
| `Regex_FindNew` | 指定查找 |
| `Regex_FindAllNew` | 文本内容提取(全部) |
| `Regex_TestNew` | 判断文本匹配 |
| `Regex_FindStrV1` | 提取文本内容 |
| `Scenario_TextComparison` | 文档比对 |
| `ScreenControl_Screenlock` | 屏幕锁屏 |
| `ScreenControl_ScreenUnlock` | 唤醒屏幕(非登录解锁) |
| `StrUtil_InStr` | 查找 |
| `StrUtil_ListToString` | 列表聚合成文本 |
| `StrUtil_CharCode` | 将ASCII码转换为文本 |
| `StrUtil_CutOutStrV1` | 截取文本 |
| `StrUtil_CreateRandomStr` | 创建随机文本 |
| `StrUtil_SplitStr` | 分割文本为列表 |
| `StrUtil_Split` | 分割字符串 |
| `StrUtil_CaseChange` | 转换大小写 |
| `StrUtil_AddToStr` | 追加文本 |
| `StrUtil_Rtrim` | 裁剪右侧空格 |
| `StrUtil_DelTrim` | 去除文本两端空格 |
| `StrUtil_Trim` | 裁剪两侧空格 |
| `StrUtil_LowerCase` | 字母转小写 |
| `StrUtil_LookUpStr` | 查找文本 |
| `StrUtil_ReverseStr` | 反转文本 |
| `StrUtil_UpperCase` | 字母转大写 |
| `StrUtil_Mid` | 取中间内容 |
| `StrUtil_CharAsc` | 将文本转换为ASCII码 |
| `StrUtil_CompleteStr` | 补齐文本 |
| `StrUtil_Replace` | 替换文本 |
| `StrUtil_Left` | 取左侧内容 |
| `StrUtil_Ltrim` | 裁剪左侧空格 |
| `StrUtil_Len` | 获取文本长度 |
| `StrUtil_Right` | 取右侧内容 |
| `System_GetEnv` | 获取环境变量 |
| `System_ReadCredential` | 获取Windows凭据 |
| `System_GetTempPath` | 获取临时文件夹路径 |
| `System_GetHomePath` | 获取用户文件夹路径 |
| `System_GetSystemPath` | 获取系统文件夹路径 |
| `System_SetEnv` | 设置环境变量 |
| `System_GetInputMethodState` | 获取输入法状态 |
| `System_ConnectRemoteDesktop` | 连接远程桌面 |
| `System_AddCredential` | 添加Windows凭据 |
| `System_SetInputMethodState` | 设置输入法状态 |
| `System_CloseRemoteDesktop` | 关闭远程连接 |
| `System_ScreenSaverShowV1` | 开启屏保 |
| `System_ExecuteCommandV1` | 执行命令行 |
| `System_DeleteCredential` | 删除Windows凭据 |
| `System_ScreenSaverStop` | 关闭屏保 |
| `System_PlaySound` | 播放音频 |
| `Time_GetDateV2` | 获取日期时间 |
| `Time_CalDate` | 计算时长 |
| `Time_WeekDay` | 取星期 |
| `Time_Day` | 取第几天 |
| `Time_Date` | 取日期 |
| `Time_FormatNew` | 将时间戳转换为日期/时间 |
| `Time_Hour` | 取时 |
| `Time_DateOffset_V1` | 增加/减少时间 |
| `Time_GetDatetimeObj` | 获取日期时间信息 |
| `Time_Format` | 时间戳转时间 |
| `Time_DatetimeToTimestamp` | 时间转时间戳 |
| `Time_TimeStamp` | 取时间戳 |
| `Time_TimeTypeConversion_V1` | 转换日期时间格式 |
| `Time_DateToTimestamp` | 将日期/时间转换为时间戳 |
| `Time_Month` | 取月份 |
| `Time_Time` | 取时分秒 |
| `Time_Year` | 取年份 |
| `Time_TimeTypeConversion` | 日期时间格式转换 |
| `Time_Now` | 取时间 |
| `Time_Second` | 取秒 |
| `Time_Minute` | 取分 |
| `WebBrowser_Switch_V1` | 切换页面 |
| `WebBrowser_SetScrollPos_V1` | 设置滚动条位置 |
| `WebBrowser_GetTAB_V2` | 获取网页 Tab 对象 |
| `WebBrowser_SaveAs_V1` | 下载文件(点击按钮) |
| `WebBrowser_GetWeblistening` | 获取监听结果 |
| `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_RunJsInIeBySelector_v3` | 执行JS |
| `WebBrowser_RunJsInIeBySelectorGround` | 执行JS |
| `WebBrowser_CreateV3` | 打开网页 |
| `WebBrowser_Reload` | 刷新 |
| `WebBrowser_CloseWeb_V2` | 关闭网页 |
| `WebBrowser_Stop` | 停止网页加载 |
| `WebBrowser_GetDocument` | 获取网页源码 |
| `WebBrowser_GetCookie` | 获取Cookies(指定) |
| `WebBrowser_DownFile_V1` | 下载文件(指定链接) |
| `WebBrowser_DownloadFile_V1` | 下载文件 |
| `WebBrowser_GetCookieNew` | 获取完整Cookies |
| `WebBrowser_GetBrowserPicV1` | 浏览器截图 |
| `WebBrowser_GetTAB` | 获取Tab页 |
| `WebBrowser_Close_V1` | 关闭浏览器 |
| `WebBrowser_Maximize` | 最大化浏览器 |
| `WebBrowser_GoForward` | 前进 |
| `WebBrowser_SetCookies` | 设置Cookies |
| `WebBrowser_StartWeblistening` | 启动网页监听 |
| `WebBrowser_Wait` | 等待网页加载 |
| `WebBrowser_UploadDialog` | 处理上传对话框 |
| `WebBrowser_DataCrawler` | 爬取数据 |
| `WebBrowser_StopWeblistening` | 停止网页监听 |
| `WebBrowser_SetCookies_V1` | 设置Cookie |
| `WebBrowser_GetTitle` | 获取Title |
| `WebBrowser_GetScrollPos_V1` | 获取滚动条位置 |
| `WebBrowser_GoBack` | 后退 |
| `WebBrowser_GoUrl` | 跳转网页 |
| `WebBrowser_BatchDataCrawl` | 批量数据采集(beta) |
| `WebBrowser_GetUrl` | 获取URL |
| `WebBrowser_GetCookies` | 获取Cookies |
| `WebBrowser_Minimize` | 最小化浏览器 |
| `Window_SizeWindow_v1` | 调整窗口大小 |
| `Window_SetWindowState_v1` | 设置窗口状态 |
| `Window_GetWindowRect` | 获取窗口范围 |
| `Window_CloseWindow` | 关闭软件窗口 |
| `Window_GetActive` | 获取活动窗口 |
| `Window_ActiveWindow_v1` | 激活窗口 |
| `Window_WindowExists` | 判断窗口存在 |
| `Window_Get` | 获取窗口 |
| `Window_SetPos` | 设置窗口 |
| `Window_GetWindow_v1` | 获取窗口对象 |
| `Window_CloseWindow_v1` | 关闭窗口 |
| `Window_GetWindowMessage_v1` | 获取窗口信息 |
| `Window_MoveWindow_v1` | 移动窗口位置 |
| `Window_ActiveWindow` | 激活软件窗口 |
| `Window_GetScreenSize` | 获取屏幕尺寸 |
| `Window_SetWindowShow` | 设置窗口是否显示 |
| `Window_WindowList` | 获取窗口对象列表 |
| `Window_GetRect` | 窗口属性 |
| `Window_MaximumSize` | 窗口最大化 |
| `Window_WaitWindow` | 等待窗口 |
| `Word_ReadSelect` | 读取选中文本 |
| `Word_SaveAs` | Word另存为 |
| `Word_SetFontName` | 设置字体 |
| `Word_CutSelect` | 剪切 |
| `Word_ReadAll` | 读取Word文本 |
| `Word_InsertPicture` | 插入Word图片 |
| `Word_SetFontStyle` | 设置字体样式 |
| `Word_New` | 新建word |
| `Word_SetAlign` | 设置对齐方式 |
| `Word_Save` | 保存Word |
| `Word_ReadTable` | 读取Word表格 |
| `Word_New_V1` | 新建Word文档 |
| `Word_Open` | 打开Word文档 |
| `Word_SelectRow` | 选择行 |
| `Word_GetPath` | 获取Word路径 |
| `Word_DeleteSelect` | 删除 |
| `Word_SetPosition` | 设置光标位置 |
| `Word_Write` | 写入文字至Word |
| `Word_PositionToSelect` | 查找文本后设置光标位置 |
| `Word_Close` | 关闭Word |
| `Word_MovePosition` | 移动光标位置 |
| `Word_SetFontColor` | 设置字体颜色 |
| `Word_CopySelect` | 复制 |
| `Word_PasteSelect` | 粘贴 |
| `Word_Replace` | 替换 |
| `Word_WriteTable` | 插入Word表格 |
| `Word_Enter` | 回车 |
| `Word_SetFontSize` | 设置字体大小 |
| `Word_SelectAll` | 全选 |
| `Word_InsertPage` | 插入分页符 |
## 隐藏重复能力
| 隐藏 ID | 保留主能力 | 名称 |
|---|---|---|
| `Basic_SetVariable` | `Basic_SetVariable_V1` | 设置变量 |
| `App_Kill` | `App_KillV1` | 关闭应用程序 |
| `Basic_Delay_V1` | `Basic_Delay_V2` | 等待 |
| `Basic_Rnd` | `Basic_RandomInt` | 生成随机数 |
| `Bivartable_CreateDf` | `Bivartable_CreateDfV1` | 创建数据表格 |
| `Bivartable_ReadDfData` | `Bivartable_ReadDfData_V1` | 读取数据 |
| `CSV_Read` | `CSV_ReadNew` | 读取内容 |
| `CSV_Write` | `CSV_WriteNew` | 写入内容 |
| `Element_SetValue` | `Element_SetValue_V1` | 文本输入 |
| `Element_Click` | `Element_Click_V1` | 元素点击 |
| `Database_AllSQLQuery` | `Database_AllSQLQuery_V1` | 执行全SQL查询 |
| `Dialog_InputBox` | `Dialog_CsInputBox` | 输入对话框 |
| `Dialog_GetConfigV5` | `Dialog_GetConfigV7` | 人机交互表单 |
| `Dialog_Notice_V1` | `Dialog_Notice_V2` | 消息通知 |
| `Dialog_Notice` | `Dialog_Notice_V2` | 消息通知 |
| `Dialog_GetConfigV6` | `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_GetConfigV4` | `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_GetConfigV3` | `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_GetConfigV2` | `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_GetConfigV1` | `Dialog_GetConfigV8` | 自定义对话框 |
| `Dialog_MsgBox` | `Dialog_CsMsgBox` | 消息确认对话框 |
| `Element_SetValuePassword` | `Element_SetValuePassword_V1` | 输入密码框 |
| `Element_Region2_v2` | `Element_Region2_v3` | 获取元素坐标 |
| `Element_Region2_v1` | `Element_Region2_v3` | 获取元素坐标 |
| `Element_Region` | `Element_Region2_v3` | 获取元素坐标 |
| `Element_Region2` | `Element_Region2_v3` | 获取元素坐标 |
| `Element_SelectOperation_v2` | `Element_SelectOperation_v3` | 下拉框操作 |
| `Element_SelectOperation_v1` | `Element_SelectOperation_v3` | 下拉框操作 |
| `Element_SelectOperation` | `Element_SelectOperation_v3` | 下拉框操作 |
| `Element_Sibling_V1` | `Element_Sibling_V2` | 获取兄弟元素 |
| `Element_Sibling` | `Element_Sibling_V2` | 获取兄弟元素 |
| `Element_Children_V1` | `Element_Children_V2` | 获取子元素 |
| `Element_ChildrenV1` | `Element_Children_V2` | 获取子元素 |
| `Element_Children` | `Element_Children_V2` | 获取子元素 |
| `Element_DesktopChildren` | `Element_ChildrenGround` | 获取子元素 |
| `Element_ElementCaptureSimilar` | `Element_ElementAarray` | 获取相似元素数组 |
| `Element_SetAttribute_v1` | `Element_SetAttribute_v2` | 设置网页元素属性 |
| `Element_SetAttribute` | `Element_SetAttribute_v2` | 设置网页元素属性 |
| `Element_WaitElement_v4` | `Element_WaitElement_v5` | 等待元素 |
| `Element_WaitElement_v3` | `Element_WaitElement_v5` | 等待元素 |
| `Element_WaitElement_v2` | `Element_WaitElement_v5` | 等待元素 |
| `Element_WaitElement_v1` | `Element_WaitElement_v5` | 等待元素 |
| `Element_WaitElement` | `Element_WaitElement_v5` | 等待元素 |
| `Element_ScrollIntoView_v1` | `Element_ScrollIntoView_v2` | 滚动页面 |
| `Element_ScrollIntoView` | `Element_ScrollIntoView_v2` | 滚动页面 |
| `Element_MouseTo_V3` | `Element_MouseTo_V4` | 鼠标移动到元素上 |
| `Element_MouseTo_V2` | `Element_MouseTo_V4` | 鼠标移动到元素上 |
| `Element_MouseTo_V1` | `Element_MouseTo_V4` | 鼠标移动到元素上 |
| `Element_MouseTo` | `Element_MouseTo_V4` | 鼠标移动到元素上 |
| `Element_Click_V5` | `Element_Click_V6` | 点击界面元素 |
| `Element_Click_V4` | `Element_Click_V6` | 点击界面元素 |
| `Element_Click_V3` | `Element_Click_V6` | 点击界面元素 |
| `Element_Click_V2` | `Element_Click_V6` | 点击界面元素 |
| `Element_Parent_V1` | `Element_Parent_V2` | 获取父元素 |
| `Element_Parent` | `Element_Parent_V2` | 获取父元素 |
| `Element_SetValue_V2` | `Element_SetValue_V3` | 输入文本框 |
| `Element_GetButtonEnable_v3` | `Element_GetButtonEnable_v4` | 判断元素是否可点击 |
| `Element_GetButtonEnable_v2` | `Element_GetButtonEnable_v4` | 判断元素是否可点击 |
| `Element_GetButtonEnable_v1` | `Element_GetButtonEnable_v4` | 判断元素是否可点击 |
| `Element_GetButtonEnable` | `Element_GetButtonEnable_v4` | 判断元素是否可点击 |
| `Element_SetValue_V5` | `Element_SetValue_V6` | 输入文本 |
| `Element_SetValue_V4` | `Element_SetValue_V6` | 输入文本 |
| `Element_GetText_V4` | `Element_GetText_V5` | 获取界面元素文本 |
| `Element_GetText_V3` | `Element_GetText_V5` | 获取界面元素文本 |
| `Element_GetText_V2` | `Element_GetText_V5` | 获取界面元素文本 |
| `Element_GetText` | `Element_GetText_V5` | 获取界面元素文本 |
| `Element_SimilarElementList_V1` | `Element_SimilarElementList_V2` | 获取相似元素列表 |
| `Element_SimilarElementList` | `Element_SimilarElementList_V2` | 获取相似元素列表 |
| `Element_Shot_V1` | `Element_Shot_V2` | 元素截图 |
| `Element_Shot` | `Element_Shot_V2` | 元素截图 |
| `Element_CheckBoxOperation_v2` | `Element_CheckBoxOperation_v3` | 勾选框操作 |
| `Element_CheckBoxOperation_v1` | `Element_CheckBoxOperation_v3` | 勾选框操作 |
| `Element_CheckBoxOperation` | `Element_CheckBoxOperation_v3` | 勾选框操作 |
| `Element_GetAttribute_v2` | `Element_GetAttribute_v3` | 获取网页元素属性 |
| `Element_GetAttribute_v1` | `Element_GetAttribute_v3` | 获取网页元素属性 |
| `Element_GetAttribute` | `Element_GetAttribute_v3` | 获取网页元素属性 |
| `Element_SetValuePassword_V5` | `Element_SetValuePassword_V6` | 输入密码 |
| `Element_GetElementV2` | `Element_GetElementNew` | 获取元素对象 |
| `Element_GetElementV1` | `Element_GetElementNew` | 获取元素对象 |
| `Element_GetElement` | `Element_GetElementNew` | 获取元素对象 |
| `Element_GetElementInfo` | `Element_GetElementInfo_V1` | 获取元素信息 |
| `Excel_Cs_WriteColumn` | `Excel_WriteColumnNew` | 写入列 |
| `Excel_WriteColumn` | `Excel_WriteColumnNew` | 写入列 |
| `Excel_Cs_SetCellFormat_V2` | `Excel_Cs_SetCellFormat_V3` | 设置单元格格式 |
| `Excel_Cs_SetCellFormat` | `Excel_Cs_SetCellFormat_V3` | 设置单元格格式 |
| `Excel_Cs_ReadContentV3` | `Excel_Cs_ReadContentV4` | 读取Excel内容 |
| `Excel_Cs_ReadContentV2` | `Excel_Cs_ReadContentV4` | 读取Excel内容 |
| `Excel_Cs_ReadContent` | `Excel_Cs_ReadContentV4` | 读取Excel内容 |
| `Excel_Cs_AutoFill` | `Excel_Cs_AutoFillV2` | 自动填充 |
| `Excel_Cs_Copy` | `Excel_Cs_CopyV2` | 复制Sheet |
| `Excel_Cs_WriteRangeV2` | `Excel_WriteRangeNew` | 写入区域 |
| `Excel_Cs_WriteRange` | `Excel_WriteRangeNew` | 写入区域 |
| `Excel_WriteRange` | `Excel_WriteRangeNew` | 写入区域 |
| `Excel_DeleteColumn` | `Excel_Cs_DeleteColumn` | 删除列 |
| `Excel_Cs_OpenV3` | `Excel_Cs_OpenV4` | 打开Excel |
| `Excel_Cs_OpenV2` | `Excel_Cs_OpenV4` | 打开Excel |
| `Excel_Cs_Open` | `Excel_Cs_OpenV4` | 打开Excel |
| `Excel_OpenExcel` | `Excel_OpenExcelNew` | 打开excel |
| `Excel_Cs_ClearRangeV3` | `Excel_Cs_ClearRangeV4` | 清除区域 |
| `Excel_Cs_ClearRangeV2` | `Excel_Cs_ClearRangeV4` | 清除区域 |
| `Excel_ClearRange` | `Excel_Cs_ClearRangeV4` | 清除区域 |
| `Excel_Cs_ClearRange` | `Excel_Cs_ClearRangeV4` | 清除区域 |
| `Excel_Cs_InsertColumnV2` | `Excel_Cs_InsertColumnV3` | 插入列 |
| `Excel_Cs_InsertColumn` | `Excel_Cs_InsertColumnV3` | 插入列 |
| `Excel_InsertColumn` | `Excel_Cs_InsertColumnV3` | 插入列 |
| `Excel_GetRowsCount` | `Excel_Cs_ReadRowCount` | 读取行数 |
| `Excel_GetColumnsCount` | `Excel_Cs_ReadColumnCount` | 读取列数 |
| `Excel_Cs_ReadCell` | `Excel_ReadCellNew` | 读取单元格 |
| `Excel_ReadCell` | `Excel_ReadCellNew` | 读取单元格 |
| `Excel_Cs_WriteCellV2` | `Excel_WriteCellNew` | 写入单元格 |
| `Excel_Cs_WriteCell` | `Excel_WriteCellNew` | 写入单元格 |
| `Excel_WriteCell` | `Excel_WriteCellNew` | 写入单元格 |
| `Excel_Cs_SetRangeFont` | `Excel_Cs_SetRangeFontV2` | 设置区域字体颜色 |
| `Excel_Cs_DeletePicture` | `Excel_Cs_DeletePictureV2` | 删除图片 |
| `Excel_DeleteImage` | `Excel_Cs_DeletePictureV2` | 删除图片 |
| `Excel_Cs_New` | `Excel_Cs_NewV5` | 新建Excel |
| `Excel_Cs_NewV4` | `Excel_Cs_NewV5` | 新建Excel |
| `Excel_Cs_NewV3` | `Excel_Cs_NewV5` | 新建Excel |
| `Excel_Cs_NewV2` | `Excel_Cs_NewV5` | 新建Excel |
| `Excel_Cs_ReadColumn` | `Excel_GetColDataNew` | 读取列 |
| `Excel_GetColData` | `Excel_GetColDataNew` | 读取列 |
| `Excel_Cs_InsertRowV2` | `Excel_Cs_InsertRowV3` | 插入行 |
| `Excel_Cs_InsertRow` | `Excel_Cs_InsertRowV3` | 插入行 |
| `Excel_InsertRow` | `Excel_Cs_InsertRowV3` | 插入行 |
| `Excel_Cs_SetHeight` | `Excel_Cs_SetHeightV1` | 设置行高 |
| `Excel_SetRowHeight` | `Excel_Cs_SetHeightV1` | 设置行高 |
| `Excel_Cs_DeleteRangeV2` | `Excel_Cs_DeleteRangeV3` | 删除区域 |
| `Excel_Cs_DeleteRange` | `Excel_Cs_DeleteRangeV3` | 删除区域 |
| `Excel_DeleteRange` | `Excel_Cs_DeleteRangeV3` | 删除区域 |
| `Excel_GetPath` | `Excel_Cs_GetPath` | 获取Excel路径 |
| `Excel_DeleteRow` | `Excel_Cs_DeleteRow` | 删除行 |
| `Excel_Cs_ReadRangeV2` | `Excel_ReadRangeNew` | 读取区域 |
| `Excel_Cs_ReadRange` | `Excel_ReadRangeNew` | 读取区域 |
| `Excel_ReadRange` | `Excel_ReadRangeNew` | 读取区域 |
| `Excel_Cs_InsertPicture` | `Excel_Cs_InsertPictureV2` | 插入图片 |
| `Excel_InsertImage` | `Excel_Cs_InsertPictureV2` | 插入图片 |
| `Excel_Cs_CopyRangeV2` | `Excel_Cs_CopyRangeV3` | 复制表格内容 |
| `Excel_Cs_CopyRange` | `Excel_Cs_CopyRangeV3` | 复制表格内容 |
| `Excel_Cs_SetWidth` | `Excel_Cs_SetWidthV1` | 设置列宽 |
| `Excel_SetColumnWidth` | `Excel_Cs_SetWidthV1` | 设置列宽 |
| `Excel_Cs_RunMacro` | `Excel_Cs_RunMacroV2` | 执行宏 |
| `Excel_ExecuteMacro` | `Excel_Cs_RunMacroV2` | 执行宏 |
| `Excel_Cs_Find` | `Excel_Cs_FindV2` | Excel查找/替换 |
| `Excel_Cs_WriteRowV2` | `Excel_WriteRowNew` | 写入行 |
| `Excel_Cs_WriteRow` | `Excel_WriteRowNew` | 写入行 |
| `Excel_WriteRow` | `Excel_WriteRowNew` | 写入行 |
| `Excel_Cs_ReadRow` | `Excel_GetRowDataNew` | 读取行 |
| `Excel_GetRowData` | `Excel_GetRowDataNew` | 读取行 |
| `Excel_Cs_MergeCells` | `Excel_Cs_MergeCellsV2` | 合并单元格 |
| `Excel_Cs_Close` | `Excel_Cs_CloseV2` | 关闭Excel |
| `Excel_Cs_SelectRangeV2` | `Excel_Cs_SelectRangeV3` | 选中区域 |
| `Excel_Cs_SelectRange` | `Excel_Cs_SelectRangeV3` | 选中区域 |
| `Excel_SelectRange` | `Excel_Cs_SelectRangeV3` | 选中区域 |
| `Excel_Cs_PastRange` | `Excel_Cs_PastRangeV2` | 粘贴表格内容 |
| `Excel_Cs_ColumnSplit` | `Excel_Cs_ColumnSplitV2` | Excel分列 |
| `Excel_Cs_Sort` | `Excel_Cs_SortV1` | Excel数据排序 |
| `Excel_Cs_SetRangeBackground` | `Excel_Cs_SetRangeBackgroundV2` | 设置区域背景颜色 |
| `Excel_Cs_Load` | `Excel_Cs_LoadV2` | 获取已打开Excel |
| `File_GetFileList` | `File_GetFileList_V1` | 获取文件列表 |
| `File_Write` | `File_Write_V1` | 写入文件 |
| `File_MakeFolder` | `File_MakeDir` | 创建文件夹 |
| `File_GetFolderList` | `File_GetFolderList_V1` | 获取文件夹列表 |
| `Http_Post_V2` | `Http_Post_V3` | POST请求 |
| `Http_Post` | `Http_Post_V3` | POST请求 |
| `Http_Get_V1` | `Http_Get_V2` | GET请求 |
| `Http_Get` | `Http_Get_V2` | GET请求 |
| `Image_Click_V1` | `Image_Click_V2` | 点击图像 |
| `Image_Click` | `Image_Click_V2` | 点击图像 |
| `Image_Find` | `Image_Find_V1` | 查找图像 |
| `Image_Wait` | `Image_Wait_V1` | 等待图像 |
| `Image_Exists` | `Image_Exists_V1` | 判断图像 |
| `Image_Move` | `Image_Move_V1` | 移动到图像 |
| `Keyboard_DriveSend_V1` | `Keyboard_DriveSend_V2` | 键盘驱动输入 |
| `Keyboard_DriveSend` | `Keyboard_DriveSend_V2` | 键盘驱动输入 |
| `Keyboard_Send` | `Keyboard_Send_V1` | 键盘输入 |
| `Mail_UnionConnect` | `Mail_RecvConnect` | 连接邮箱 |
| `Mail_ReplyMail_v1` | `Mail_ReplyMail_v2` | 回复邮件 |
| `Mail_ReplyMail` | `Mail_ReplyMail_v2` | 回复邮件 |
| `Mail_UnionSend_v1` | `Mail_Send_V1` | 发送邮件 |
| `Mail_Send` | `Mail_Send_V1` | 发送邮件 |
| `Mail_UnionSend` | `Mail_Send_V1` | 发送邮件 |
| `Mail_FindMail` | `Mail_FindMail_v1` | 查找邮件 |
| `Mouse_ClickCombine_v3` | `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_ClickCombine_v2` | `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_ClickCombine_v1` | `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_Click` | `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_ClickCombine` | `Mouse_ClickCombine_v4` | 鼠标点击 |
| `Mouse_DragToV2` | `Mouse_DragToNew` | 鼠标拖拽 |
| `Mouse_DragToV1` | `Mouse_DragToNew` | 鼠标拖拽 |
| `Mouse_DragTo` | `Mouse_DragToNew` | 鼠标拖拽 |
| `Mouse_MoveToV1` | `Mouse_MoveToV2` | 移动鼠标 |
| `Mouse_MoveTo` | `Mouse_MoveToV2` | 移动鼠标 |
| `PDF_SetPdfWaterMark` | `PDF_SetPdfWaterMarkV1` | PDF 加水印 |
| `PDF_ConcatPdf` | `PDF_ConcatPdfV1` | 合并 PDF 文档 |
| `Regex_Find` | `Regex_FindNew` | 指定查找 |
| `StrUtil_CutOutStr` | `StrUtil_CutOutStrV1` | 截取文本 |
| `System_ScreenSaverShow` | `System_ScreenSaverShowV1` | 开启屏保 |
| `System_ExecuteCommand` | `System_ExecuteCommandV1` | 执行命令行 |
| `Time_GetDateV1` | `Time_GetDateV2` | 获取日期时间 |
| `Time_GetDate` | `Time_GetDateV2` | 获取日期时间 |
| `Time_DateOffset` | `Time_DateOffset_V1` | 增加/减少时间 |
| `WebBrowser_Switch` | `WebBrowser_Switch_V1` | 切换页面 |
| `WebBrowser_SetScrollPos` | `WebBrowser_SetScrollPos_V1` | 设置滚动条位置 |
| `WebBrowser_GetTAB_V1` | `WebBrowser_GetTAB_V2` | 获取网页 Tab 对象 |
| `WebBrowser_SaveAs` | `WebBrowser_SaveAs_V1` | 下载文件(点击按钮) |
| `WebBrowser_BatchDataCrawlNew` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCollectionV4` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCollectionV3` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCollectionV2` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_BatchDataCrawlNewV1` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCollectionV1` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCollection` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCrawl` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCrawl_software` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_DataCrawl_web` | `WebBrowser_DataCollectionV5` | 数据采集 |
| `WebBrowser_RunJsInIeBySelector_v2` | `WebBrowser_RunJsInIeBySelector_v3` | 执行JS |
| `WebBrowser_RunJsInIeBySelector_v1` | `WebBrowser_RunJsInIeBySelector_v3` | 执行JS |
| `WebBrowser_RunJsInIeBySelector` | `WebBrowser_RunJsInIeBySelector_v3` | 执行JS |
| `WebBrowser_RunJsInIeByTitle` | `WebBrowser_RunJsInIeBySelector_v3` | 执行JS |
| `WebBrowser_CreateV2` | `WebBrowser_CreateV3` | 打开网页 |
| `WebBrowser_Create` | `WebBrowser_CreateV3` | 打开网页 |
| `WebBrowser_CreateBackground` | `WebBrowser_CreateV3` | 打开网页 |
| `WebBrowser_CloseWeb_V1` | `WebBrowser_CloseWeb_V2` | 关闭网页 |
| `WebBrowser_CloseWeb` | `WebBrowser_CloseWeb_V2` | 关闭网页 |
| `WebBrowser_DownFile` | `WebBrowser_DownFile_V1` | 下载文件(指定链接) |
| `WebBrowser_DownloadFile` | `WebBrowser_DownloadFile_V1` | 下载文件 |
| `WebBrowser_GetBrowserPic` | `WebBrowser_GetBrowserPicV1` | 浏览器截图 |
| `WebBrowser_Close` | `WebBrowser_Close_V1` | 关闭浏览器 |
| `WebBrowser_GetScrollPos` | `WebBrowser_GetScrollPos_V1` | 获取滚动条位置 |
| `Window_SizeWindow` | `Window_SizeWindow_v1` | 调整窗口大小 |
| `Window_SetWindowState` | `Window_SetWindowState_v1` | 设置窗口状态 |
| `Window_SetActive` | `Window_ActiveWindow_v1` | 激活窗口 |
| `Window_GetWindow` | `Window_GetWindow_v1` | 获取窗口对象 |
| `Window_Close` | `Window_CloseWindow_v1` | 关闭窗口 |
| `Window_GetWindowMessage` | `Window_GetWindowMessage_v1` | 获取窗口信息 |
| `Window_MoveWindow` | `Window_MoveWindow_v1` | 移动窗口位置 |
机器可读决策:`resources/capability-duplicates.json`。

View File

@ -1,119 +0,0 @@
# AI 拾取模型选型与 RPA 执行方案
## 结论
当前模型“只能识别控件”不是解析器写错,而是 OmniParser 的 icon detect 本身就是单类别模型。它适合解决“屏幕上有哪些可交互区域”,不适合直接回答“这是一个登录按钮还是搜索框”。
更合适的不是换一个全能小模型,而是分四层:
1. 视觉定位:YOLO/OmniParser 找 bbox,保证又快又能看到 UIA 看不到的区域。
2. 视觉语义:OCR 读取框内文本;必要时用小分类模型或图标匹配补充类别。
3. UIA 增强:用 AI bbox 中心回查 Windows UIA,拿到 `Name / ControlType / AutomationId / ClassName`。
4. RPA 执行:优先保存 UIA selector,AI bbox 只作为兜底坐标和运行时重定位依据。
这才是对齐实在这类产品的方向:AI 负责感知和定位,RPA 引擎负责稳定执行和状态反馈。
## 当前可试模型
模型已经放在 `D:\code\PRA-only-demo\temp\models`。同名 `*.labels.json` 已经配好,当前实验台会读取标签,不再显示“类别1/类别2”。
| 本地文件 | 来源 | 类别数 | 类别 | 速度/体积 | 适用场景 | License 风险 |
|---|---|---:|---|---|---|---|
| `omniparser-v1-fp16.onnx` | `onnx-community/OmniParser-icon_detect` | 1 | `控件` | 约 6 MB,测试约 0.8s | 可交互区域粗定位、基线对比 | 商用前复核 OmniParser/底模授权 |
| `amigo-ui-elements-yolov8.onnx` | `amigodev/ui-elements-detector-test` | 8 | `button/field/heading/iframe/image/label/link/text` | 约 12 MB,测试约 0.4s | 网页截图、轻量多类别试验 | 仓库未标 License,仅试验 |
| `webpage-element.onnx` | `OpenDILabCommunity/webpage_element_detection` | 8 | `button/field/heading/iframe/image/label/link/text` | 约 99 MB,测试约 1.5s | 网页 UI,准确率通常优于小模型 | Apache-2.0 |
| `android-ui-yolov8n.onnx` | `yasirfaizahmed/android_ui_detection_yolov8` | 21 | `TextButton/EditText/CheckBox/Switch/Toolbar` 等 | 约 12 MB,测试约 0.4s | 安卓/移动 UI;桌面也可当轻量 UI 分类基线 | Apache-2.0 |
| `macpaw-ui-elements-yolo11l.onnx` | `macpaw-research/yolov11l-ui-elements-detection` | 5 | `AXButton/AXDisclosureTriangle/AXImage/AXLink/AXTextArea` | 约 102 MB,测试约 1.7s | 桌面/辅助功能导向,语义更接近可访问性控件 | AGPL-3.0,商用谨慎 |
| `resources/models/ui-element-detect/webpage-element.onnx` | OpenDILab webpage | 8 | `button/field/heading/iframe/image/label/link/text` | 当前默认,约 99 MB | 已实测并接入 Pane AI 拾取 | Apache-2.0 |
| `resources/models/ui-icon-detect/model.onnx` | OmniParser v2 icon detect | 1 | `控件` | 旧链路兼容回退 | 仅缺新模型时兜底 | 商用前复核 |
测试方式:打开“AI 识别实验台”,选择识别目标,点“选择 ONNX”,选择上表中的 `.onnx` 文件。确保同名 `.labels.json` 放在同一目录。
## 实测观察
同一张简单网页风格测试图:
- Android UI YOLOv8n:约 431ms,能识别 `TextButton`、`EditText`、`Text`,置信度普遍高。
- Amigo UI YOLOv8:约 430ms,能识别 `button`、`field`,框更整齐。
- OpenDILab webpage:约 1533ms,类别与 Amigo 相同,体积和耗时更大。
- MacPaw YOLO11l:约 1743ms,能识别 `AXButton`、`AXTextArea`,但对该测试图置信度偏低。
- OmniParser v1:约 781ms,只输出“控件”,定位能力可用但没有类别语义。
这组结果说明:轻量 YOLO 完全能做到实时/近实时,速度问题不是模型架构本身,而是模型大小、截图裁剪、进程通信和重复初始化共同决定的。
## 当前 RPA 可用性
当前 Pane AI 拾取生成:
```text
screen=x,y,width,height;window=...;name=OCR文本或类别;type=视觉类别;aiType=视觉类别;aiText=OCR文本;ai=1
```
`webpage-element.onnx` 先定位候选;PaddleOCR/PP-OCRv4 对同一张 Pane 截图做一次整图识别,并按几何关系把文本写回候选框。整图结果按截图 SHA-256 缓存,重复识别同一画面不会重复推理。执行层已经支持以 `screen=` 做坐标点击、坐标输入、坐标取值,所以“立刻执行一次”是可用的。
但只靠当前 selector 还不能算稳定 RPA 元素,原因是:
- `screen=` 是拾取瞬间的坐标快照。
- 窗口移动、页面滚动、分辨率/DPI 变化后会失效。
- `exists/wait` 目前对 `screen=` 过于宽松。
- `name/type` 很多时候只是视觉模型类别,不是业务语义。
- 没有运行时重定位机制。
## 目标 selector 结构
建议逐步变成:
```text
window=title:xxx;process=xxx;
type=Button;name=登录;id=loginButton;class=Button;
aiType=button;aiRect=x,y,w,h;aiModel=webpage-element;
template=sha1:...;ai=1
```
定位策略:
1. 优先按 `window + AutomationId + Name + ControlType` 查 UIA。
2. UIA 查不到时,限制在原窗口/原 Pane 附近重找。
3. 再失败时截图并运行视觉模型,在候选中按 `aiType + IoU + 外观相似度` 找回目标。
4. 找回后更新真实 UIA rect,再执行动作。
## AI 类别到 RPA 动作映射
| AI/UIA 类别 | RPA 默认动作 |
|---|---|
| `button / AXButton / TextButton` | `Element_Click` |
| `field / EditText / AXTextArea / TextBox` | `Element_SetValue` / `Element_GetValue` |
| `link / AXLink` | `Element_Click` |
| `text / label / heading` | `Element_GetText` / OCR |
| `CheckBox / Switch` | `Element_SetCheck` / `Element_GetCheck` |
| `Spinner / ComboBox` | `Element_SetSelect` / 展开后点击 |
| `image / Icon` | 坐标点击 / 图标匹配 |
| `Toolbar / Navigation / Group` | 作为容器遍历子元素,不直接执行动作 |
## 最好用的落地路线
### 第一阶段:可用
- 已完成:本地模型、Pane AI、候选框、悬浮确认。
- 已完成:候选类别从 sidecar 读取,默认模型切换为 `webpage-element.onnx`。
- 已完成:PaddleOCR 整图识别、候选框文本分配、OCR 结果缓存和异步回填。
- 已完成:`F2/F4` 进入 AI;`Esc/F9` 取消 AI;`F8` 切换开发候选框。
- 补 UIA 回查:选中框后用中心点/范围找最小 UIA 元素,生成稳定 selector。
### 第二阶段:稳定
- `exists/wait` 真正支持 `screen= + window + aiRect`。
- 执行前校验窗口和元素仍在预期区域。
- 增加 AI 重定位:截图 → 检测 → 与保存的 bbox/裁剪图匹配 → 更新坐标。
- 所有执行结果回写目标快照,方便排查框偏。
### 第三阶段:接近实在
- 元素检测、元素分类、OCR、图标相似、UISelector 组合。
- AI 只输出结构化 target/action,不直接控鼠标键盘。
- RPA 执行器负责窗口激活、等待、重试、截图、异常恢复。
- 对复杂远程桌面/Canvas/Java 应用保留视觉兜底。
## 推荐优先级
已选定 `webpage-element.onnx` 作为 Pane AI 默认模型。其他模型继续留在本地 `temp/models` 供实验台对比,不再进入默认链路和安装包。内置 OmniParser 只作为老安装包兼容回退,不建议作为最终业务 selector 的唯一来源。

788
docs/RPA-Review-Full.md Normal file
View File

@ -0,0 +1,788 @@
# RPA 项目技术审查与 Agent-Employee 集成实施总方案
> 文档性质:统一总方案 + 最新源码复审记录。
> 审查对象:资料库中的 `PRA-only-demo-main(2).zip`(本轮解压源码快照)。
> 审查重点:RPA Core 边界、运行时安全、能力注册、驱动注入、平台适配、元素拾取、Agent-Employee 接入。
> 结论状态:静态源码审查;未将未执行的测试写成通过。
## 0. 执行摘要
这份文档统一此前几轮讨论、项目原有技术文档和两轮源码审查结论。它不是“项目已经成熟”的背书,而是当前代码能否安全演示、能否抽成可复用 RPA Core、能否接入 Agent-Employee 的事实清单与实施路线。
**总体判断:**项目已经从单纯 Electron Demo 走到有流程引擎、能力注册、驱动接口、元素拾取和能力目录的阶段;但当前 `@rpa/core` 还不能被认定为真正独立、可注入、可跨平台的执行内核。最主要的阻塞不在于“再加多少能力”,而在于执行取消/重试安全、Engine/Registry/Driver 生命周期、Electron 依赖残留、包依赖闭包和宿主治理契约。
当前建议:
1. **Windows 10/11 演示优先**,先收敛一条稳定端到端流程,不要为了演示临时扩展 Linux/macOS。
2. **接入 Agent-Employee 前先修 P0/P1**,尤其是取消与重试、Driver 注入、动态 Electron 依赖和包依赖闭包。
3. **先做垂直切片**:Gateway 授权 → RPA Runtime → 一个文件能力 → 一个浏览器能力 → 一个桌面能力 → 事件/证据回传,再逐批搬能力。
4. **跨平台按能力域分批实现**。当前代码是 Windows-first,不是 Windows/Linux/macOS 三端可用;类型里出现平台占位不代表已有实现。
## 1. 项目目标与已确认约束
以下是此前项目讨论中已经明确的目标,不是本轮从代码推断出来的事实:
- 项目定位:面向办公自动化的 RPA 平台,后续接入已有 Agent-Employee / opencode 底座。
- 用户负责 RPA 部分;商业项目已有独立的服务端 AI 组件,RPA Core 不应重复承担服务端模型部署职责。
- 总体方向:**AI 模式 hybrid;部署方式 local;跨平台目标 all**。
- 元素拾取策略:**本地优先、云端兜底**;hover 实时识别高亮,click 确认。
- 目标软件范围:Office、浏览器、Electron、Qt 以及自绘界面。
- 识别策略:UIA/Accessibility 等结构化接口优先,OCR/视觉模型补足;不把 YOLO 训练作为前置条件,也不把 SAM 作为当前主链路。
- 当前实际交付重点:Windows 10/11;Linux/macOS 是后续架构目标,不应在未完成驱动与平台能力矩阵前宣传已支持。
- 技术现状:Electron + Node.js + React + TypeScript;RPA Core 位于 `packages/rpa-core`,宿主 UI/拾取窗口仍在 `src/main` / renderer。
## 2. 当前架构画像
```text
Electron Desktop Host
├─ Renderer / Flow Canvas / Properties UI
├─ Main Process
│ ├─ flow persistence / settings / logging
│ ├─ desktop picker / overlay / vision lab
│ └─ host bridge initialization
└─ @rpa/core (workspace package)
├─ Flow Runtime / expression / error model
├─ module-global Capability Registry
├─ capability handlers (files, HTTP, Office, desktop, OCR, vision...)
├─ WindowsDriver / PowerShell helpers
├─ HostBridge / SubflowSource module singletons
└─ Browser CDP transport / external browser launcher
```
### 2.1 已经具备的基础
- 有独立的 `packages/rpa-core` 目录和包名 `@rpa/core`。
- 有 Flow Runtime、RunContext、NodeState、RuntimeError、能力注册表与能力元数据。
- 有 Windows PowerShell driver 封装及鼠标、键盘、窗口、屏幕、UIA 五域接口定义。
- 有 `registerAll()` 能力分组加载入口。
- 有 HostBridge / SubflowSource 抽象,减少部分宿主 API 的直接依赖。
- 有元素拾取相关代码、OCR/视觉能力、能力目录和较多单元/冒烟测试。
### 2.2 不能据此宣称已经完成的事项
- Core 仍有动态 `import("electron")`,不是 Electron-free。
- `createEngine()` 当前没有真正接收 Driver;`registerAll(engine)` 也没有把注册表绑定到该 Engine。
- 多个能力仍直接依赖 PowerShell、Win32、COM、Electron 屏幕截图 API。
- 当前没有 MacDriver / LinuxDriver。
- `@rpa/core` 的 package manifest 没有声明源码实际使用的全部外部依赖。
- 尚未完成 Agent-Employee 真实契约的编译级对接。
- 本轮未运行 Vitest、typecheck、Electron build、Bun test、Windows 实机 E2E。
## 3. 本轮继续审查新增发现
### P1-NEW-01:RPA Core 内仍有动态 Electron 依赖,依赖护栏漏检
**证据:**
- `packages/rpa-core/src/capabilities/vision.ts:18,66,432`:动态 `import("electron")`,读取 `desktopCapturer` / `screen`。
- `packages/rpa-core/src/capabilities/cv-vision.ts:51,213`:动态 `import("electron")`,获取截图与 DIP 坐标转换。
- `packages/rpa-core/scripts/check-deps.mjs:20-20,38-43`:当前扫描表达式只匹配静态 `import/export ... "specifier"`,不匹配 `import("electron")`。
- `packages/rpa-core/src/engine/host-bridge.ts:6-8` 与 `docs/rpa-core-migration-guide.md:28` 却声称 Core 禁止 Electron / 完全不依赖 Electron。
**影响:**
- Node/Bun 独立运行时,调用视觉能力时会尝试加载 Electron;CLI 只跑未触发这些分支的能力,不能证明整个 Core 脱离 Electron。
- Agent-Employee backend 若不是 Electron 主进程,视觉能力会在运行时失败或无法复用。
- 依赖护栏“通过”目前只能说明它没有识别到静态违规,不能作为边界验收证据。
**整改:**
1. 将屏幕截图、显示器信息、DPI 换算抽成 `ScreenCaptureHost` / `DisplayCoordinateService` 接口,由 Electron Host 实现注入。
2. `vision.ts`、`cv-vision.ts` 只依赖接口,不再动态加载 Electron。
3. 依赖护栏补动态 import、`require()`、`createRequire()` 与 `import()` 字符串扫描;最好使用 TypeScript AST,而不是继续堆正则。
4. CI 增加一个纯 Node/Bun 进程加载 Core 并实际调用视觉 Host mock 的测试。
**验收:**在没有 Electron 包可解析的 Node/Bun 环境下,Core 能加载;视觉能力在 Host 未注入时返回明确的 `host_service_unavailable`,注入 Fake Host 后可完成截图/坐标测试。
### P1-NEW-02:`@rpa/core` 包依赖声明不完整
**证据:**
- `packages/rpa-core/src/capabilities/vision.ts` 使用 `@napi-rs/canvas`。
- `packages/rpa-core/src/capabilities/pdf.ts` 使用 `pdfjs-dist`、`@napi-rs/canvas`、`@cantoo/pdf-lib`。
- `packages/rpa-core/package.json` 未声明上述三个依赖;它们目前只出现在根项目依赖(其中 `@cantoo/pdf-lib`、`@napi-rs/canvas`、`pdfjs-dist`)。
- `package-lock.json` 的 `packages/rpa-core` workspace 条目同样未列出这些依赖。
**影响:**当前 monorepo 根目录安装时可能通过依赖提升“碰巧可用”,但把 `packages/rpa-core` 单独迁入 Agent-Employee 或单独安装时,相关能力可能出现 `Cannot find package`。这属于包边界和发布闭包问题。
**整改:**逐个核对 Core 的运行时外部依赖;凡 Core 源码运行时 import 的依赖,要么声明在 Core dependencies/optionalDependencies,要么从 Core 移除并由 Host 注入。原生模块需额外验证目标 OS 与 Bun/Node 兼容性。
**验收:**在隔离目录只安装 `@rpa/core` 声明的依赖,运行包加载、PDF/OCR/图像相关代表能力;不得依赖根项目的幽灵依赖。
### P1-NEW-03:`registerAll(engine)` 参数和 Engine 实例没有实际隔离效果
**证据:**
- `packages/rpa-core/src/capabilities/register-all.ts:49-56` 接收 `engine`,但只执行模块动态 import,最终仅 `return engine`。
- `packages/rpa-core/src/engine/registry.ts:33-34` 的 handlers/metas 是模块级 Map。
- `packages/rpa-core/src/engine/runtime.ts:260` 从全局 `getHandler()` 取 handler。
- `packages/rpa-core/src/engine/engine.ts:45-49` 的 `driver` 固定为 `new WindowsDriver()`,`createEngine()` 无注入参数。
- `packages/rpa-core/src/capabilities/desktop.ts:9`、`common-final.ts:11`、`system-extended.ts:36`、`window-extended.ts:7` 读取 `defaultEngine.driver`。
**影响:**调用者可能以为每个 Engine 有独立的 Driver、能力注册表和权限域,实际执行仍共享进程级注册表和默认 WindowsDriver。多 Agent、多桌面会话、测试并行、不同租户策略会互相污染。`createEngine()` 目前更像状态容器,不是完整隔离的执行实例。
**整改:**
1. 先确定目标架构:建议 `RpaEngine` 持有 Registry、Driver、HostServices、SessionContext。
2. `createEngine({ driver, host, registry, ... })` 显式注入。
3. handler 注册写入 `engine.registry`;Runtime 从 `ctx.engine` 或构造参数拿 handler,不读全局 Map。
4. 能力 handler 不捕获 `defaultEngine.driver`;改为从 `RunContext` 注入的 execution services 读取。
5. 过渡期如保留全局 API,明确标记 deprecated,不能让 `registerAll(engine)` 假装实例化注册。
**验收:**创建两个 Engine,分别注入 FakeDriverA/B 并注册不同能力集;并行运行时互不串用 Driver、handler、HostBridge、子流程源。
### P1-NEW-04:CLI 冒烟测试不能证明 Engine 注入和 Core 独立性
**证据:**
- `scripts/rpa-cli.ts:24-25` 同时 `import { ... } from "@rpa/core"` 和 `import "@rpa/core/capabilities"`(副作用全量注册)。
- `scripts/rpa-cli.ts:47-49` 随后调用 `createEngine()` / `registerAll(engine)`,但该参数没有绑定 Registry。
- `scripts/rpa-cli.ts:53-58` 通过全局 `getHandler()` 执行 handler,而不是通过 Engine 获取。
- `scripts/rpa-cli.ts:5-10` 声称 5 项代表能力,实际执行了 Window_GetActiveWindow、Window_GetScreenSize、Excel_Cs_NewV5、File_Write、File_Read、Http_Get 共 6 次 handler 调用。
**影响:**CLI 可以验证某些能力在当前 Node + 根目录依赖 + Windows 环境下能运行,但不能验证 Driver 注入、Engine 隔离、显式注册、无 Electron 依赖或独立包依赖闭包。文档将它当成“零宿主独立运行”的证明,结论过度。
**整改:**移除副作用 import;测试只使用显式注册;通过 Engine API 获取 handler 并运行;测试 FakeDriver 注入;将 CLI 验收拆成纯 Node、Windows Host、Electron Host 三种,不把联网 HTTP 作为唯一固定验收依赖。
### P1-NEW-05:迁移文档与任务清单互相矛盾,完成度表述不可信
**证据:**
- `docs/rpa-core-migration-guide.md:22` 写“改造已全部完成”;第 28 行写 Core 完全不依赖 Electron。
- `docs/rpa-core-refactor-tasks.md:17-91` 阶段 0 至阶段 6 的任务仍全部保留 `[ ]` 未勾选。
- 源码又存在动态 Electron import、Driver 注入缺失、包依赖缺项。
**影响:**接手者会按“已完成”理解架构状态,低估接入工作量;也无法判断哪些是设计目标、哪些是已验收能力。
**整改:**统一文档状态标记:`已实现(代码定位)`、`已测试(测试证据)`、`已在目标宿主验证`、`计划中`。每项完成必须附 commit / 测试命令 / 测试结果,不以文档描述代替代码事实。
## 4. 元素拾取与视觉定位:统一技术路线
元素拾取不应被等同为“找一个 AI 模型识别按钮”。办公 RPA 需要的是可执行、可复定位的 Target,而不仅是截图框。
### 4.1 建议识别优先级
1. **结构化可访问性接口**:Windows UIA;浏览器 DOM/CDP;macOS AX;Linux AT-SPI。
2. **OCR + 几何关系**:用于文字、标签、按钮文本与相邻控件关系,输出文字和 bounding box。
3. **免训练视觉候选检测**:当前模型实验可作为候选框生成器,但不要将模型类别直接当成业务语义。
4. **模板/局部视觉匹配**:对图标、无文本控件和自绘 UI 作兜底。
5. **人工确认**:候选冲突、低置信度或高风险动作时要求用户确认。
### 4.2 Selector 应保存“语义 + 定位证据”,而不是只存坐标
建议 Target 结构至少包含:
- `platform / app / process / window`:目标环境身份;
- `accessibility`:AutomationId、Name、ControlType、ClassName、祖先路径;
- `browser`:URL/域名、frame、DOM role/aria-label/test id 等;
- `ocr`:识别文本、语言、置信度、相对区域;
- `visual`:bbox、截图裁剪 hash、模型标识、相似度;
- `fallback`:最后一次屏幕坐标、DPI/缩放、窗口矩形、采集时间。
运行时定位顺序:结构化 selector → 同窗口/同 frame 限定查找 → OCR/视觉重定位 → 低置信度时停止并要求确认。禁止将拾取时 `screen=x,y,w,h` 当作长期稳定定位。
### 4.3 当前代码复核原则
- 之前报告指出的 `candidateId` 回填错位问题,在本次 ZIP 中已看到映射修复,不重复当作当前缺陷。
- `desktop-pick.ts` 中候选排序、筛选与执行 selector 的一致性仍需专项回归,尤其 Edge debug/CDP、DPI、多屏和 hover 高频刷新。
- `vision.ts` / `cv-vision.ts` 的 Electron 直接调用应迁到 Host 服务,不能放在可复用 Core 中。
- OCR 是语义补充,不应单独负责确定控件类型;模型候选类别也不能直接映射成“登录按钮”等业务含义。
## 5. Agent-Employee 集成目标架构
```text
Agent-Employee Workflow / Agent Runtime
│
▼
RPA Adapter(唯一入口)
├─ Workflow/Step 状态映射
├─ Capability Gateway:授权、租约、审批、审计
├─ 参数/返回值 Schema 转换
├─ Trace / Event / Evidence 关联
└─ 取消、超时、幂等与重试策略
│
▼
RPA Core(每次执行/会话实例)
├─ Flow Validator
├─ Runtime / Expression / Error Contract
├─ Engine-scoped Registry
├─ Capability Handlers
└─ Execution Context(AbortSignal / Trace / Permission / Evidence)
│
├──────── Host Services(宿主注入)
│ ├─ Browser Adapter
│ ├─ Screenshot / Display / DPI
│ ├─ Dialog / AI Config / Subflow Store
│ └─ Event / Evidence Sink
│
└──────── Computer Control Driver
├─ WindowsDriver(当前优先)
├─ MacDriver(后续)
└─ LinuxDriver(后续)
```
**权责原则:**
- Agent-Employee Workflow/Runtime 是宿主最终 Run/Step 状态的唯一权威来源;RPA Runtime 作为执行器或执行子流程,不另建第二套冲突状态系统。
- Capability Gateway 是所有带副作用能力的唯一授权入口。RPA Core 不再另建一套与其冲突的 allowlist。
- computer-control 是桌面观察/定位/动作契约的优先对齐对象;不要让 RPA Driver 与宿主契约长期并行。
- HostServices 与 Driver 按 Engine/Session 注入,不使用模块级全局 setter 承载多租户/多桌面会话状态。
- 高风险能力(命令执行、键鼠注入、文件删除/覆盖、发送邮件/消息、数据库写操作)必须有权限、审批/租约、审计和资源范围。
## 6. 当前问题优先级总表
| 优先级 | 问题 | 接入影响 | 建议门槛 |
|---|---|---|---|
| P0 | 超时只让外层 Promise reject,底层操作不一定停止 | 可能重叠执行、重复副作用 | 接入前修复 |
| P0 | 取消状态与 NodeState 不一致;取消可能被当失败/跳过 | 宿主状态与执行状态冲突 | 接入前修复 |
| P0 | retry 默认策略过宽,非幂等动作可能重试 | 重复点击/输入/发信/覆盖 | 接入前修复 |
| P0 | Engine 无 Driver 注入;能力使用 defaultEngine | 无法按会话隔离/测试替身 | 接入前修复 |
| P1 | Registry 模块级全局 Map,registerAll(engine) 不实例化注册 | 多 Agent/多权限域污染 | 接入前明确并改造 |
| P1 | HostBridge / SubflowSource 模块级单例 | 多宿主/多会话覆盖 | 接入前改造 |
| P1 | vision/cv-vision 动态依赖 Electron | Core 不能独立运行 | 迁移前修复 |
| P1 | Core manifest 缺失 canvas/pdf 依赖 | 单包安装不完整 | 迁移前修复 |
| P1 | CLI 依赖全局注册,不能证明注入/独立性 | 现有验收证据不足 | 重写验收 |
| P1 | Flow Graph 无完整 Validator | 多出口/非法边可能静默部分执行 | 接入前补校验 |
| P1 | CapabilityMeta 缺权限/副作用/幂等字段 | Gateway 映射不完整 | Gateway 对接前补齐 |
| P1 | Windows 专属能力散落在能力层 | 平台能力边界不清 | 平台适配前整理 |
| P1 | catalog platformTag 类型异常 | 平台过滤/界面标记不可信 | 修复并加 schema test |
| P1 | Agent-Employee 契约尚未逐接口编译验证 | 估算与实现存在偏差 | 联调前必须做 |
| P1 | BrowserSession 的 locate 与执行查找器语义不一致 | 拾取后 XPath/Shadow DOM 选择器可能执行失败 | 浏览器能力验收前修复 |
| P1 | iframe 未纳入拾取/定位/执行上下文 | iframe 内元素无法保证端到端可用,跨域 iframe 尤其明显 | 浏览器能力验收前明确支持边界 |
| P1 | CDP 单条命令无响应超时 | 浏览器命令可能永久 pending,流程卡死 | 接入前补统一超时与清理 |
| P1 | 外部浏览器“前台标签跟随”取 pages[0] | 多标签时可能切错目标页 | 多标签回归前修复 |
| P1 | AI 拾取取消未贯穿检测/OCR任务 | 取消后后台推理仍可能继续,OCR 等待无硬超时 | 拾取稳定性验收前修复 |
| P2 | 依赖护栏只扫静态 import,且只覆盖 TS/TSX | 边界回归容易漏检 | 尽快增强 |
| P2 | AI 请求没有调用方 AbortSignal | 拾取取消不能立即中止请求 | 交互体验整改 |
| P2 | 包没有独立 build/pack 验收 | 迁移消费方式不稳定 | 发布前补齐 |
## 7. 执行安全整改方案
### 7.1 取消必须贯穿整条调用链
`RunContext.signal` → Runtime → Handler → Host/Driver → PowerShell 子进程 / HTTP / Browser CDP。每层都必须接收 signal,并在取消时清理资源。
PowerShell 子进程需要持有 ChildProcess 引用,收到 abort 后执行 terminate/kill;仅设置 `execFile` timeout 不等于响应上游取消。浏览器动作、等待、网络请求、OCR/AI 请求也应尽量可取消。
### 7.2 超时不等于操作已停止
当前 `withTimeout()` 只让外层 Promise reject,不保证底层 handler 结束。超时后必须先 abort 底层任务并等待其停止/清理,再决定是否重试;不能直接开始第二次点击或输入。
### 7.3 重试必须基于能力语义
新增能力元数据:`sideEffect`、`idempotent`、`retryPolicy`、`permission`、`approvalRequired`、`resourceScope`。非幂等能力默认不重试;只有明确幂等、可安全重复的操作才能自动重试。对“发送消息/邮件、提交表单、点击付款、删除/覆盖文件”等默认禁止盲目重试。
### 7.4 状态机
至少统一 `pending → running → succeeded | failed | cancelled | skipped`。取消不能被转成普通失败,也不能误走错误分支。RunResult、NodeState、Agent-Employee Step 状态需要一份映射表和契约测试。
## 8. 平台适配路线
### 8.1 当前定位
当前实际是 Windows-first:PowerShell、Win32、UIA、Office COM、Windows 剪贴板和屏幕 API 占据大量路径。`platforms: ["win32"]` 的类型声明只是当前标记,不是跨平台实现。
### 8.2 建议分阶段
- **阶段 A:Windows 可靠性**:Windows 10/11、UIA、Edge/Chrome CDP、Office COM、DPI、多显示器、PowerShell 取消/超时、安装包资源。
- **阶段 B:Linux 桌面基础**:先选 X11 或 Wayland 的支持边界;AT-SPI、输入注入、截图、窗口管理分别实现;Wayland 必须明确权限与限制。
- **阶段 C:macOS 桌面基础**:Accessibility API、CGEvent/AppleScript、TCC 辅助功能和屏幕录制授权流程。
- **阶段 D:Office/文件能力矩阵**:Windows COM;macOS AppleScript/Office;Linux LibreOffice 或标记不支持。每项能力逐个标平台,不能按“同名软件存在”推断等价。
### 8.3 平台标记单一来源
`register-all.ts` 的能力分组、CapabilityMeta.platforms、catalog.platformTag 必须由一份平台能力表派生;当前发现 `catalog.json` 中 1016 项里 1013 项 `platformTag=null`,另 3 项是数字 `1`,与 `src/types.ts` 的 `string | null` 不符。`build-catalog.mjs` 的 `component.platformTag || ...` 会保留 truthy 数字,必须增加 schema 校验并修复源数据。
## 9. 推荐实施顺序与阶段门禁
### 阶段 0:先固化演示基线(Windows)
- 冻结一个端到端演示流程,准备稳定的测试软件、数据和账号。
- 把必需节点与不稳定节点分开;演示脚本不得依赖外网随机响应。
- 验收:连续完整执行多轮;每轮有运行日志、节点状态、失败截图/证据;取消后无残留子进程/后台动作。
### 阶段 1:Core 接入前 P0
- 贯通 AbortSignal 和底层取消。
- 取消状态正确映射。
- 重试策略按幂等性约束。
- Engine 真正支持 Driver/Host/Registry 注入。
- 验收:FakeDriver、超时、取消、重试、双 Engine 隔离测试全绿。
### 阶段 2:包边界闭环
- 移除 Core 动态 Electron import。
- 补齐/收敛 package dependencies。
- 依赖扫描升级为 AST 或覆盖动态 import/require。
- 重写 CLI,去掉副作用全量 import,验证 Engine-scoped registry。
- 验收:隔离安装、Node/Bun 纯 Core 加载、Host Mock 视觉路径。
### 阶段 3:Agent-Employee 契约对齐
- 获取目标仓库真实源码与当前 commit。
- 对 `workflow-engine`、`capability-gateway`、`computer-control`、`windows-plugin`、`browser-plugin` 的类型、生命周期、事件和错误模型逐接口对照。
- 输出 Adapter 映射表与 ADR,不根据迁移指南中的摘要直接开始实现。
- 验收:编译通过、Gateway 拒绝时 handler 零调用、事件/证据可追踪。
### 阶段 4:小范围垂直切片
- 先接一个纯文件能力、一个浏览器能力、一个桌面能力。
- 跑通授权、租约、审批、执行、取消、事件、证据回传。
- 验收后才开始分批迁移能力组。
### 阶段 5:能力规模化与跨平台
- 能力分组迁移、平台标记、风险元数据补齐。
- Linux/macOS 按驱动和能力矩阵逐步开放,不做虚假全平台开关。
## 10. 交付给接入团队的材料清单
1. 本总方案(本文件)。
2. `docs/RPACore二次源码审查与接入整改报告.md`:此前两轮源码审查细项与原始定位,作为附录参考。
3. `docs/rpa-core-migration-guide.md`:现有迁移指南,但其中“完全不依赖 Electron / 已全部完成”等表述应按本总方案纠正。
4. `docs/rpa-core-refactor-tasks.md`:原改造任务清单,当前勾选状态与迁移指南冲突,应重新按证据更新。
5. `docs/06-AI拾取模型选型与RPA执行方案.md`、`docs/图像与OCR问题检测报告.md`:拾取与视觉路线参考,需结合当前实现再次做 Windows 实机回归。
## 11. 本轮审查边界
- 已读取资料库中的最新 `PRA-only-demo-main(2).zip`,并检查 `packages/rpa-core`、核心运行时、注册入口、Driver、依赖护栏、CLI、catalog 和项目文档。
- 已核验 `check-deps.mjs` 的实现逻辑;该脚本的扫描方式确实漏掉动态 `import("electron")`。
- 未安装项目依赖;未执行 Vitest、TypeScript typecheck、Electron build、Bun test、打包和 Windows 实机 E2E。不得把这些项目标成“通过”。
- 当前没有 Agent-Employee 目标仓库真实源码作为本轮输入,因此本文件给出的是接入架构和待核验契约清单,不代表已完成目标仓库编译对接。
- ZIP 中不包含 `node_modules`;本轮静态结论以该 ZIP 源码快照为准。
---
# 附录 A:此前二次源码审查细项
以下保留此前审查报告的完整细项,包括已修复问题回归、P0/P1/P2 代码定位、Graph Validator、HostBridge、Registry、平台标记、能力元数据和验收标准,避免统一文档时丢失前几轮审查证据。
# RPACore 二次源码审查与 Agent-Employee 接入整改报告
> 审查对象:资料库中的 `PRA-only-demo-main(2).zip`(约 154.8 MB)
> 审查日期:2026-11-06
> 审查范围:`packages/rpa-core/`、流程运行时、能力注册、Driver/HostBridge、能力目录、桌面拾取与打包配置、原有迁移文档
> 审查方式:源码静态审查 + 项目结构/JSON/脚本检查;未在 Windows 真实桌面环境执行端到端测试。
> 重要说明:本报告区分“源码已确认”“设计风险/需产品语义确认”“尚未运行验证”,不把静态推断写成运行结果。
---
## 0. 总体结论
当前项目已经不是简单的 Electron 自动化 Demo:它有独立的 `packages/rpa-core` workspace、流程运行时、能力注册表、五域 Driver 类型、HostBridge、外部浏览器 CDP、桌面 UIA/视觉拾取以及较多能力测试。**现有技术栈不需要推翻,真正需要解决的是运行时语义、依赖注入闭环、能力治理和平台边界。**
但当前状态还不适合直接宣称“可无修改迁入 Agent-Employee”或“已具备跨平台能力”。建议定位为:**Windows-first 的 RPA 能力包,已建立初步包边界,仍需完成执行语义与宿主契约收口。**
### 本轮核验到的项目事实
- ZIP 内约 272 个条目、约 230 个文件;`packages/rpa-core/src` 包含 35 个 `*.test.ts` 文件。
- `packages/rpa-core` 的包名仍是 `@rpa/core`,`private: true`,入口直接指向 `src/index.ts`,尚未形成面向普通 Node/Bun 部署的独立构建产物契约。
- `node packages/rpa-core/scripts/check-deps.mjs` 本次执行通过;这只证明该脚本当前扫描规则未发现违规 import,不等于包完全没有宿主/平台耦合。
- `resources/catalog.json` 声明 1016 个组件:1013 个 `platformTag` 为 `null`,3 个 `platformTag` 是数字 `1`,与 `CatalogComponent.platformTag: string | null` 类型不一致。
- `electron-builder.config.mjs` 当前已经把 `resources/desktop-picker.ps1` 放入 `extraResources`;AI 候选覆盖层当前用 `candidateId` 回传并按 ID 查找原候选。这两项是旧版元素拾取报告中的 P0,本次最新快照中已看到对应修正,不应继续作为“当前未修复问题”重复上报。
### 建议优先级
| 优先级 | 主题 | 结论 |
|---|---|---|
| P0 | 超时、取消与重试的副作用安全 | 先处理;否则自动化可能出现重复点击/重复写入 |
| P0 | Engine/Driver/Registry/HostBridge 依赖闭环 | 接入前必须明确,否则宿主无法可靠隔离会话与权限域 |
| P1 | 流程图结构校验和执行语义 | 防止无效图被静默部分执行 |
| P1 | 能力元数据与 Capability Gateway | Agent 不应绕过授权直接调用高影响能力 |
| P1 | 平台目录、Driver 边界与 Windows 专属能力 | 不应把“抽象接口存在”描述成 Linux/macOS 已支持 |
| P1 | 包构建、Bun 兼容和集成验收 | 迁入前必须有目标运行时证据 |
| P2 | 依赖扫描增强、注册冲突治理、可观测性 | 提高长期维护质量 |
---
## 1. P0:执行安全与依赖闭环
### P0-01 超时只终止 Runtime 等待,不保证底层能力停止
**源码位置**:`packages/rpa-core/src/engine/runtime.ts:33-55, 279-307`;`packages/rpa-core/src/engine/types.ts:118-137`;`packages/rpa-core/src/driver/windows/powershell.ts:45-69`。
**确认事实**:`withTimeout()` 通过计时器拒绝外层 Promise;它没有创建并传递专属 AbortController,也没有在超时后通知 handler 停止。`RunContext` 虽然有 `signal`,但 `AutomationDriver` 的五域方法没有 `signal` 参数,PowerShell helper 也没有接受调用方 AbortSignal 的接口。PowerShell helper 自己有 30 秒等进程超时,但这与 Runtime 节点超时不是同一个取消链路。
**影响**:节点超时后 Runtime 可能已经开始重试,前一次 PowerShell/桌面操作却仍在执行。对点击、输入、删除、覆盖文件、发邮件、提交表单等非幂等动作,可能产生重复副作用。用户按取消后,运行状态结束也不代表底层操作已经停止。
**整改**:
1. 为每次节点尝试创建独立 `AbortController`,把 Run 级 signal 与 Node 级 timeout 合并。
2. Handler/Driver/PowerShell/HTTP/浏览器操作逐层接收 signal;可取消的操作必须真实中止。
3. 对不能中止的原生/外部动作,标记为 `nonCancellable`,超时后不得自动重试,必须等待进程退出或进入人工恢复状态。
4. 将重试策略从“按错误码猜测是否可重试”改为能力元数据声明:`idempotent`、`retryPolicy`、`sideEffectLevel`。
5. 默认重试次数设置保守上限;当前 `retryCount` 被允许到 100,且 `isRetryable()` 将大多数未知错误视为可重试,不适合作为有副作用能力的默认策略。
**验收**:构造一个执行 5 秒的 Fake Handler,节点 timeout 设 100ms;验证 100ms 后 handler 收到 abort 并停止;再构造“第一次执行延迟完成、第二次执行”的测试,证明不会发生并发重入。对文件写入/点击类能力增加“超时不自动重试”测试。
### P0-02 取消时节点状态可能记录为 failed,而非 cancelled
**源码位置**:`runtime.ts:326-351`;`types.ts:139-160`。
**确认事实**:`executeNode()` 捕获异常后,普通错误分支会将状态设为 `failed` 或 `skipped`;`RuntimeError("cancelled")` 没有在节点状态层单独映射为 `cancelled`。Run 层随后可能返回 `status: cancelled`,但对应 NodeState 仍显示 failed/skipped。
**影响**:Agent-Employee 若要做步骤级恢复、审批、审计和用户界面状态展示,会遇到 Run 已取消但 Step 显示失败的语义冲突。
**整改**:在 `executeNode` catch 中优先判断 `cancelled`,写入 `state.status = "cancelled"`,不执行 `skipOnError` 和普通重试;统一发出 cancelled 事件,并记录取消原因/发起方。
**验收**:在节点执行中取消;断言 Run 与对应 NodeState 都是 cancelled,后续节点不执行,且不进入 error route。
### P0-03 Engine 创建接口没有真正的 Driver 注入能力
**源码位置**:`engine.ts:24-53`;`driver/types.ts:1-10, 158-165`;`packages/rpa-core/README.md:50-57`。
**确认事实**:`RpaEngine.driver` 类字段固定 `new WindowsDriver()`;`createEngine()` 无参数。Driver 类型注释和 README 却声称 `createEngine()` 可注入自定义 Driver。
**影响**:不能注入 Agent-Employee `computer-control` 适配器,也不能在单测中使用 Fake Driver;非 Windows 平台创建 Engine 时仍会实例化 Windows 实现。
**整改**:提供 `createEngine({ driver, registry, hostBridge, subflowSource, ... })`,将默认 Driver 的选择放在平台工厂或宿主装配层;Engine 不应在跨平台包内部无条件创建 WindowsDriver。
**验收**:Fake Driver 注入测试覆盖 mouse/keyboard/window/screen/element;Linux/macOS 上若无 Driver,明确返回 `platform_unsupported`,不得误报初始化成功。
### P0-04 Registry 与 Engine 生命周期不一致,`registerAll(engine)` 参数没有生效
**源码位置**:`registry.ts:33-62`;`register-all.ts:49-57`;`runtime.ts:3, 260`。
**确认事实**:handlers/metas 是模块级 Map;`registerAll(engine)` 只动态 import 模块,能力模块通过全局 `register()` 写入同一个 Registry;`runFlow()` 用全局 `getHandler()` 查找 handler。传入的 Engine 不决定注册目标。
**决策背景**:原迁移指南明确选择过“保留全局注册表”。因此问题不是简单的代码疏漏,而是既有决策带来的隔离限制。
**整改建议**:
- 短期单后端、单能力集合:可保留进程级 Registry,但把 API 改成语义真实的 `loadCapabilities(groups)`,文档明确不支持 Engine 级能力隔离。
- 长期多 Agent/多租户/不同权限域:改成 `CapabilityRegistry` 实例,由 Engine 持有;显式注册函数接收 Registry,运行时从 Engine Registry 取 handler。
**验收**:至少有两个 Engine A/B 的注册隔离测试(若采用实例级方案);若暂时采用全局方案,测试需明确证明共享行为是预期设计,并验证重复注册、重复加载和冲突覆盖规则。
### P0-05 HostBridge/SubflowSource 是模块级单例
**源码位置**:`engine/host-bridge.ts`(HostBridge 与 SubflowSource 的模块级变量/set/get);`ai-service.ts:20-24`;`human-dialog.ts`;`window-extended.ts`。
**确认事实**:宿主桥通过模块级变量保存,`setHostBridge()` / `getHostBridge()` 读取同一进程级对象;子流程源也采用相同模式。
**影响**:多桌面会话、多 Engine、测试并行或多宿主容器时,后一次注入会覆盖前一次配置;AI 配置、DPI 换算、弹窗窗口工厂可能串到错误会话。
**整改**:把 HostServices 放入 Engine 构造依赖;若宿主明确只有一个全局桌面会话,可保留进程级服务,但应在 ADR 中限定部署模型,并提供 `reset/restore` 测试机制。不要让业务 handler 在模块导入时捕获全局桥对象。
**验收**:两个 Engine 注入不同 HostBridge,分别触发 AI 配置读取/DPI 转换/弹窗,结果不能交叉。
---
## 2. P1:流程图语义与运行时完整性
### P1-01 执行器缺少运行前 Graph Validator
**源码位置**:`runtime.ts:178-219`;`types.ts:115-145`;`runtime.error-route.test.ts:51-75`。
**确认事实**:有边时,运行时选择一个起点(优先无入边 Trigger,其次任意无入边节点,再退回 `top[0]`),然后沿边递归执行;普通边通过 `edges.find(...)` 只取第一条非 error 边。访问过的节点由 `visited` 集合直接跳过。当前测试明确验证断开的 orphan 节点不执行,因此“孤立节点不执行”是现有行为,不应未经产品确认就当作 Bug。
**风险**:如果画布允许多个入口、一个节点多个普通出口、重复边、目标不存在、普通边形成环,运行时可能只执行其中一条路径或静默停止,且没有在执行前给出结构错误。流程编辑器允许的图结构与 Runtime 支持的图结构需要有统一契约。
**整改**:新增 `validateFlowGraph(flow)`,运行前检查:节点 ID 唯一;边 source/target 必须存在;入口数量与 Trigger 规则一致;普通出口基数符合节点类型;error handle 唯一;不允许意外环;容器子节点 slot 合法;孤立节点明确警告或拒绝。若业务确实支持多出口/并行,必须实现明确的调度语义,不能用 `find()` 随机取第一条。
**验收**:非法多出口、孤立节点、环、重复 ID、悬空边均在执行前得到稳定错误码;合法 IF/循环/异常分支测试保持通过。
### P1-02 `skipOnError`、错误分支与 Stop/Break/Continue 的状态语义需统一
**源码位置**:`runtime.ts:141-154, 203-211, 326-351`。
**风险**:目前错误路由、skipOnError、Break/Continue/Stop 都在不同层捕获;需要明确一个节点失败后何时走 error edge、何时 skip 并走 normal edge、何时终止整个 Run。特别要保证 cancelled 不会被当作普通可跳过错误。
**整改**:定义一张状态转移表并由测试固定:成功→normal;可跳过失败→skipped+normal;不可跳过失败且有 error edge→error;无 error edge→Run failed;cancelled→Run cancelled 且不走 error edge;Stop→按产品定义终止状态;Break/Continue 只能在循环上下文捕获。
---
## 3. P1:能力元数据、安全治理与平台支持
### P1-01 能力目录平台标记数据异常
**源码位置**:`resources/catalog.json`;`src/types.ts:49-70`;`scripts/build-catalog.mjs:234-249`。
**确认事实**:当前 catalog 有 1016 个组件,其中 1013 个 `platformTag=null`,3 个值为数字 `1`(`Basic_BusinessOrderCount`、`Dialog_GetConfigV7`、`Dialog_GetConfigV5`),但类型要求 `string | null`。构建脚本的 `component.platformTag || ...` 会把 truthy 的数字 1 原样保留,无法靠类型声明自动纠正 JSON 数据。
**影响**:前端/Agent 不能可靠判断某个能力在哪个平台可执行;类型层与运行时 JSON 事实不一致。
**整改**:平台支持使用统一枚举 `win32 | darwin | linux` 或 `null`;生成 catalog 时做 schema 校验并拒绝非字符串值;平台能力映射只保留一个权威来源,运行时元数据与构建期目录从同一份数据派生。当前只声明 Windows 已实现,其他平台标记为未支持,不要为了“跨平台目标”提前填支持标记。
**验收**:catalog 构建后 1016 条均通过 schema 校验;每个能力都有明确平台状态;Windows 专属能力不会被误标为跨平台。
### P1-02 CapabilityMeta 不足以承载 Agent-Employee 权限治理
**源码位置**:`registry.ts:7-31`;能力目录 `CatalogComponent.risk` 在 `types.ts:65`。
**确认事实**:Registry 的元数据目前包含 summary/params/returns/timeout/errorCodes/needsScreenshot/platforms;目录只有 `risk: low | high`。未看到统一的权限范围、审批要求、幂等性、副作用类型、资源访问范围等完整治理元数据。
**影响**:Agent-Employee 的 `capability-gateway` 无法仅凭当前元数据可靠地决定授权、审批、租约范围和审计字段;若直接暴露全部 handler,会绕过宿主治理边界。
**整改**:在宿主适配层建立唯一 `CapabilityRunner`:授权 → 租约/策略校验 → 输入校验 → 执行 → 证据/审计。为能力补充结构化治理元数据,建议包含 `riskLevel`、`permissions`、`approvalRequired`、`sideEffect`、`idempotent`、`platforms`、`timeoutBudget`;字段最终以 Agent-Employee 契约为准。高影响能力不得通过全局 `getHandler()` 直接暴露给 Agent。
**验收**:命令执行、键鼠输入、文件删除/覆盖、邮件发送、网络写操作等均经过 Gateway;审批拒绝/租约过期时 handler 不会执行;审计可关联 Agent、Run、Capability、审批结果和执行结果。
### P1-03 Driver 抽象尚未形成平台隔离边界
**源码位置**:`driver/types.ts:133-165`;`driver/windows/windows-driver.ts`;`capabilities/desktop-uia.ts`;`capabilities/vision.ts`、`cv-vision.ts`、`remaining-core.ts`、`system-extended.ts`、`word.ts` 等。
**确认事实**:五域接口存在,但 ScreenDriver 目前只有 `size()`;注释明确截图等 System.Drawing 操作仍可留在能力层。ElementDriver 通过 `setElementDriverImplementation()` 模块级 setter 接到 WindowsDriver;多个能力仍直接调用 PowerShell、Win32、COM 或 Windows 剪贴板。
**影响**:接口“看起来五域齐全”不等于能力层已平台无关。未来增加 Mac/Linux Driver 时,若不先盘点调用点,平台差异会继续散落在能力 handler 中。
**整改**:按能力建立 Windows-only 白名单;把截图、剪贴板、窗口/元素观察、键鼠动作、Office COM 等原语逐项归入 Driver/Host adapter;对不适合抽象的能力显式标记平台限制。不要为了抽象而一次性重写全部能力,按高频基础原语先收口。
**验收**:Core 通用层静态扫描不允许出现未列入白名单的 Windows API/PowerShell/COM 调用;Fake Driver 可覆盖核心自动化能力。
### P1-04 跨平台目标与当前实现状态需要分开表达
**确认事实**:当前 `WindowsDriver` 使用 `powershell.exe`、`user32.dll`、Windows UIA、Office COM 等;外部浏览器启动和系统能力也存在 Windows 专属实现。没有看到可用的 `MacDriver` / `LinuxDriver`。
**建议表述**:项目目标是 Windows/Linux/macOS;当前可交付实现以 Windows 为主。Linux 需先明确 X11/Wayland 边界,macOS 需处理 Accessibility、Screen Recording 等系统授权;Office 自动化三端也不是同一套实现。不要将“接口预留”写成“已支持跨平台”。
---
## 4. P1/P2:包工程化与质量护栏
### P1-05 `@rpa/core` 还不是完整的可发布包
**源码位置**:`packages/rpa-core/package.json`。
**确认事实**:包名为 `@rpa/core`,`private: true`,`main` / `types` / `exports` 直接指向 TypeScript 源文件;包脚本没有 build、pack 或 exports 产物检查。
**影响**:在当前 workspace/Bun 环境中可能可以直接消费 TS 源码,但不能据此推断它可被普通 Node.js、生产打包器或独立服务稳定消费。Agent-Employee backend 使用 Bun 的事实仍需在目标仓库实测。
**整改**:确定宿主采用源码 workspace 依赖还是构建产物依赖。若源码消费,锁定 Bun 版本并建立 Bun test/typecheck/运行测试;若产物消费,补 `tsc/tsup` 构建、`exports` 的 types/import/default 条目、`files` 白名单和 `npm pack --dry-run` 检查。
### P2-01 依赖护栏是正则扫描,不是完整依赖边界证明
**源码位置**:`packages/rpa-core/scripts/check-deps.mjs:20-55`。
**确认事实**:脚本扫描 `.ts/.tsx`,用正则匹配静态 `import/export ... from`;没有覆盖 `import("electron")`、CommonJS `require()`、`createRequire()`、运行时拼接加载、`.mjs/.js` 文件,也没有校验包外部依赖 allowlist。当前脚本执行通过。
**整改**:保留快速扫描,同时增加 TypeScript AST 扫描;对 `import()`、require、createRequire 和禁用模块路径进行检查;另加 `package.json` 依赖审计和构建后 bundle 检查。脚本通过只能写成“当前规则扫描通过”,不要写成“彻底无 Electron 依赖”。
### P2-02 Registry 重复注册与元数据覆盖规则不明确
**源码位置**:`registry.ts:36-41`。
**确认事实**:同 ID 再次注册会直接覆盖 handler;如果第二次注册没有 meta,旧的 meta 不会删除,可能出现 handler 已更新但 meta 仍是旧值。
**整改**:定义重复注册策略(拒绝/显式覆盖/版本化);覆盖 handler 时同步处理 meta;注册冲突输出可审计信息。
### P2-03 `registerAll()` 可能发生部分注册
**源码位置**:`register-all.ts:49-56`。
**确认事实**:按传入顺序逐组 import;如果前面几个组已加载、后续遇到未知组才抛错,会留下部分全局注册状态。
**整改**:先校验全部 group 名称,再执行加载;长期方案采用显式 Registry,支持事务式注册或失败回滚。
### P2-04 AI 请求缺少调用方取消信号
**源码位置**:`capabilities/ai-service.ts:39-76`。
**确认事实**:AI 请求使用 `AbortSignal.timeout(timeoutMs)`,但 `ChatCompletionOptions` 没有调用方 `signal` 字段。交互式请求默认超时 45 秒;UI 拾取取消时不能从该 API 直接中止 HTTP 请求。
**影响**:界面退出后请求仍可能继续占用网络/服务端资源;连续识别可能造成无用请求并发。
**整改**:增加 `signal?: AbortSignal`,把用户取消与内部 timeout 合并;新请求开始时按业务策略取消旧请求;对 408/429/5xx 以外的错误明确重试策略。
---
## 5. 上一版元素拾取报告的回归核验
以下项目在 2026-11-01 的旧报告中列为高优先级,本次针对最新 ZIP 复核后状态如下:
| 旧问题 | 最新源码核验 | 当前结论 |
|---|---|---|
| `desktop-picker.ps1` 未列入 electron-builder `extraResources` | `electron-builder.config.mjs:26-32` 已包含 `resources/desktop-picker.ps1` | 已修复,仍需安装包实测资源路径 |
| 候选框排序后用数组下标回填错位 | `desktop-pick.ts:238-253, 596-616` 使用 `candidateId` 识别和回查候选 | 已看到修复,不应再列为当前 Bug |
| 视觉坐标执行路径与 UIA 执行路径不一致 | 旧报告指出过此风险;本次未完成所有 selector source 到执行器分发的逐函数回归 | 待专项复核,不在本报告宣称已修复 |
| AI 请求超时/取消 | Core AI API 目前有 timeout,但没有外部 AbortSignal | 仍有取消链路缺口,见 P2-04 |
元素拾取总体仍建议维持“UIA 快路径 → 局部 OCR/CV → 低频 AI 兜底”的方向。普通 hover 不应触发大模型;AI 候选应绑定稳定 candidateId,并最终形成可重新定位的 ElementDescriptor,而不是只保存一次性屏幕坐标。
---
## 6. 推荐目标架构与接入边界
```text
Agent-Employee workflow-engine
|
v
RPA Adapter / CapabilityRunner
- 权限授权、租约、审批、审计
- Run/Step 状态映射
- 输入输出 schema 适配
|
v
RPA Core Engine (per execution/session)
- Flow Validator + Runtime
- CapabilityRegistry(建议长期实例级)
- RunContext (signal / trace / evidence / variables)
|
+---- Capability Handlers
| - pure Node capabilities
| - Windows-only capability allowlist
|
+---- AutomationDriver (injected)
| - WindowsDriver
| - LinuxDriver (future)
| - MacDriver (future)
|
+---- HostServices (injected)
- Browser adapter
- Dialog / DPI / config
- Subflow repository
- Event / evidence sink
```
**职责原则**:
- workflow-engine 是宿主权威 Run/Step 生命周期的候选所有者;RPA Core 提供流程执行算法或节点执行器。最终只能有一个权威状态源。
- Capability Gateway 是所有高影响能力的唯一授权入口;Core 不另建一套冲突的权限系统。
- computer-control 是桌面观察/定位/动作契约的优先对齐对象;RPA Core 的 Driver 不应与宿主契约长期并行演化。
- HostServices/Browser/子流程存储由宿主注入,Core 不直接依赖 Electron BrowserWindow 或应用全局状态。
---
## 7. 整改顺序与验收门槛
### 第一阶段:执行安全(接入前)
- [ ] P0-01 节点级 AbortSignal 贯通 Runtime → Handler → Driver/PowerShell/HTTP/Browser。
- [ ] P0-02 取消状态映射一致,取消不触发 skip/error route/retry。
- [ ] P0-03 非幂等能力默认不重试;能力元数据声明幂等性与重试策略。
- [ ] P1-01 Graph Validator 覆盖入口、出口、环、悬空边、孤立节点和容器 slot。
### 第二阶段:依赖闭环(接入前)
- [ ] P0-04 明确 Registry 是进程级还是 Engine 级,并更新 API/ADR。
- [ ] P0-05 Engine 构造时注入 Driver 与 HostServices。
- [ ] P1-02 `registerAll()` 与实际注册作用域一致,重复注册规则明确。
- [ ] P1-03 建立 Capability Gateway 适配层和权限元数据映射。
### 第三阶段:平台与包工程化(联调阶段)
- [ ] P1-04 catalog 平台标记修复并增加 schema 校验。
- [ ] P1-05 完成 Driver/Windows-only 能力白名单。
- [ ] P1-06 选定 Bun 源码消费或构建产物消费方案,执行 Bun typecheck/test/运行验证。
- [ ] P2-01 AST 依赖检查覆盖动态 import/require。
- [ ] P2-02 AI 取消信号与拾取流程取消联动。
### 接入验收最低标准
1. Core 在目标 Bun 版本下 typecheck/test 通过;不能以 Node 测试代替 Bun 验证。
2. Fake Driver 注入成功,核心能力不启动真实 PowerShell。
3. 两个 Engine 的依赖/状态隔离行为符合 ADR。
4. 所有有副作用能力经过 Capability Gateway,权限拒绝时 handler 零调用。
5. 取消/超时/重试测试证明不会重复执行非幂等动作。
6. Graph Validator 能阻止非法流程在执行前运行。
7. catalog 平台字段通过 schema 校验,平台支持范围与真实实现一致。
8. Windows 10/11 实机完成 UIA、浏览器、Office、DPI、多屏幕、安装包资源和取消场景回归。
---
## 8. 需要与 Agent-Employee 团队共同确认的 ADR
| ADR | 必须确认的问题 | 建议 |
|---|---|---|
| Registry 生命周期 | 一个 backend 是否存在多个 Agent/租户/权限域? | 长期采用 Engine 级 Registry;短期单进程固定能力集可临时全局共享 |
| Runtime 所有权 | workflow-engine 还是 RPA Core 持有 Run/Step 最终状态? | 宿主持有权威状态,Core 负责执行并回传事件 |
| Driver 契约 | RPA Driver 是否直接实现 computer-control? | 对齐宿主契约,避免双份抽象 |
| HostServices 作用域 | 一个进程是否会管理多个桌面会话? | 按 Engine/Session 注入,不使用全局 setter |
| 能力权限 | 哪些能力需要审批/租约/资源范围? | Capability Gateway 唯一入口 |
| 包消费方式 | Bun 直接消费 TS 源码还是 Core 构建后消费? | 以 backend 生产部署方式决定,并建立 CI 证明 |
| 平台范围 | 本轮交付是否只要求 Windows? | 当前明确 Windows-first,Linux/macOS 分阶段验收 |
---
## 9. 审查限制与下一步
- 本次成功读取了资料库中的最新 ZIP,并对关键文件做静态核验。
- 本次执行了 `check-deps.mjs`,结果通过。
- ZIP 未包含 `node_modules`,当前审查环境没有安装项目依赖,因此没有执行 Vitest、TypeScript typecheck、Electron build 或 E2E;这些结果均标记为“未验证”,不能写成通过。
- 没有 Agent-Employee 目标仓库源码可供本轮逐接口编译对接;其契约映射仍以原迁移指南中记录的接口信息为基础,正式集成前必须在目标仓库核对真实类型定义。
- 没有在真实 Windows 10/11 桌面执行 UIA、Office COM、Edge/CDP、多屏/DPI、安装包和 AI 取消链路。
**最终建议**:先修 P0 的取消/超时/重试和 Engine 依赖注入,再决定 Registry 作用域;不要先把 1016 个能力整体搬入宿主。先用一条小而完整的垂直切片(一个流程、一个浏览器能力、一个桌面能力、一个文件能力)跑通 Gateway → Runtime → Driver → Event/Evidence,再逐批迁移能力。
## 12. 第三轮深挖:Runtime、浏览器执行链与元素拾取
> 本节是对同一源码快照的进一步静态追踪。这里区分“代码路径已确认”和“运行时表现仍需实机复现”;没有把未执行的 Edge/Office/UIA 测试写成已复现故障。
### 12.1 P1:BrowserSession 的定位与执行查找器不一致,拾取结果不保证可执行
**源码证据:**
- `src/main/browser/session.ts:61-85`:`selectorSpec()` 接受 `css`、`xpath`、`text`、`aria` 等类型,并可接收拾取器产生的 `css` selector。
- `src/main/browser/session.ts:243-291`:`withElementOnce()` 对 XPath 使用 `document.evaluate()`;对 CSS `>>>` 分段逐层进入 `shadowRoot`。
- `src/main/browser/session.ts:463-503`:`locateOnce()` 对 `text`、`aria` 有分支,但其余类型统一执行 `document.querySelectorAll(spec.value)`。因此 `xpath` 会被当作 CSS selector;`css` 中的 `>>>` 也会被当作普通 CSS 字符串。
- `src/main/browser/session.ts:562-565`:`click()` 首先调用 `locate()`,之后才调用 `withElement()`。
- `src/main/browser/session.ts:623-627`:`type()` 同样先调用 `locate()`。
- `src/main/browser/session.ts:672-681`:`getText(selector, attribute)` 的属性分支再次直接调用 `document.querySelector(spec.value)`。
**结论:**
1. `xpath://...` 即使能通过 `withElement()` 找到,也会在 `click()` / `type()` 前置的 `locate()` 阶段走 CSS 查询路径。
2. 拾取器会生成包含 Shadow DOM 路径分隔符 `>>>` 的 selector;`withElement()` 支持该语法,但 `locate()` 与 `getText(attribute)` 不支持。
3. 因此“拾取器生成了 selector”不等于“所有浏览器操作都能消费这个 selector”。这属于明确的实现不一致,不是单纯的测试缺失。
**整改:**抽出唯一的 `resolveElement(selector, context)`,统一支持 CSS/XPath/Text/ARIA/Shadow DOM;`locate/click/type/getText/getRegion/getElementInfo` 全部复用同一个解析器。不要让每个方法各自拼 DOM 查询表达式。
**验收:**同一批 fixture 分别覆盖 CSS、XPath、text、ARIA、open Shadow DOM、多层 Shadow DOM;每个 selector 至少通过 locate、click、type、getText、getRegion。测试断言拾取产出的 selector 能被执行器直接消费。
### 12.2 P1:iframe 未进入浏览器拾取与执行上下文模型
**源码证据:**
- `PICKER_BOOTSTRAP_SOURCE`(`src/main/browser/session.ts:1390` 起)将拾取脚本注入当前页面 `document`,使用 `document.elementFromPoint`、`event.composedPath()` 和 `document.querySelectorAll()`。
- `selectorSpec()` / `withElementOnce()` / `locateOnce()` 均以当前 target 的主 `document` 为根,没有 frameId、frame URL、frame path 或 frame-specific execution context。
- `getElement()` 生成的 CSS 路径只描述当前 document / ShadowRoot 树,不包含 iframe 边界。
**结论:**当前代码有 Shadow DOM 的部分穿透设计,但没有 iframe frame tree 的处理。跨域 iframe 内元素不能由顶层页面脚本直接访问;同源 iframe 也没有被当前 picker 主动递归遍历。不能把“支持 Shadow DOM”宣传成“支持 iframe”。
**整改:**基于 CDP `Page.getFrameTree` 建立 frame registry;为每个可访问 frame 建立 execution context,selector reference 携带 frameId 与 frame 内 selector;跨域 iframe 通过 CDP frame execution context 定位,而不是依赖顶层 JS 的同源访问。对于无法接管的特殊 frame,明确返回 unsupported / inaccessible。
**验收:**同源 iframe、跨域 iframe、嵌套 iframe、iframe 内 Shadow DOM 分别测试拾取、定位、点击、输入;页面导航和 frame 重建后 frameId 必须刷新。
### 12.3 P1:外部 CDP 单条命令没有超时和 pending 清理
**源码证据:**
- `packages/rpa-core/src/browser/transport.ts:96-117`:`send()` 将请求写入 `pending`,等待对应 response;没有单次请求 timer / AbortSignal。
- 只有 WebSocket close/error 时 `failAllPending()` 才会清理 pending(`transport.ts:76-78, 160-165`)。
- `transport.ts:83-90`:`connect()` 用 `Promise.race()` 做连接超时,但超时分支没有清理 timer,也没有关闭已经创建的 WebSocket transport。
**影响:**CDP 某条命令若既不返回响应、WebSocket 又没有断开,调用会一直 pending;上层 Runtime 的 `withTimeout()` 只能让节点 Promise 超时,不能清掉 transport 内部 pending 项。连接超时后底层 socket 也可能继续存活。
**整改:**`send(method, params, { timeoutMs, signal })` 为每个 request 建 timer;超时后删除 pending 并 reject;连接超时必须清 timer、关闭 socket;所有 timer 在成功、失败、close 三条路径都清理。超时错误要保留 method、targetId、requestId。
**验收:**模拟不返回 response、WS 断线、连接握手超时、主动 close;断言 Promise 在期限内结束、pending 数回到 0、socket 不泄漏。
### 12.4 P1:所谓“跟随前台标签页”实际上取 `/json/list` 的第一项
**源码证据:**
- `src/main/browser/session.ts:205-213` 的 `followForegroundTab()` 调用 `listPageTargets()` 后直接取 `pages[0]`。
- `src/main/browser/session.ts:1293-1305` 的 picker `followForegroundTab()` 同样取 `pages[0]`。
- `packages/rpa-core/src/browser/external.ts:242-250` 的 `listPageTargets()` 只是按 `/json/list` 返回顺序过滤并映射,没有查询实际窗口焦点或活动 target。
- `listTabs()` 的 `active` 字段只是比较 `page.id === this.activeTargetId`(`session.ts:1186-1193`),代表当前 CDP session 绑定页,不代表用户实际看到的前台页。
**结论:**注释中的“前台标签页”与实现语义不一致。多标签场景下 `/json/list` 第一项不保证是当前活动标签页,可能切错页面,造成操作落在用户看不到的页面。
**整改:**明确产品语义到底是“用户前台页”还是“流程当前绑定页”。若要求真实前台页,使用可验证的活动 target 机制(并处理 Chromium 版本差异);若只是流程绑定页,删除“前台跟随”逻辑和误导性注释,要求通过 targetId 明确切页。
**验收:**至少 3 个标签页,交替切换前台页,比较用户可见页、CDP 当前页、RPA active 标记;点击新标签、页面跳转、关闭标签时检查绑定状态。
### 12.5 P1:AI 桌面拾取取消未贯穿检测和 OCR,存在后台任务继续运行的路径
**源码证据:**
- `src/main/desktop-pick.ts:480-490`:`detection` 先启动截图与 `detectUiElements()`,OCR `recognizePane()` 并行启动;这两个任务都没有接收 AbortSignal。
- `desktop-pick.ts:506-528`:取消 watcher 只与 `detection` 做 `Promise.race()`;取消先发生时只结束外层等待,不会停止检测/截图/OCR底层工作。
- `desktop-pick.ts:557-589`:OCR 后台任务独立运行并更新 overlay。
- `desktop-pick.ts:613-614`:用户确认候选后直接 `await detected.ocrTask`,没有额外硬超时。
- `src/main/local-vision/pane-ocr-service.ts:88-112`:`recognizePane()` 等待模型初始化和 `ocr.detect()`,接口没有 signal / timeout。
**影响:**用户取消时,CPU/GPU 推理或 OCR 仍可能继续;overlay 已销毁时后台检测后续仍可能访问窗口对象;用户确认后 OCR 若卡住,拾取流程可能超过 UI 的 180 秒候选等待期限继续挂起。
**整改:**为拾取建立统一 `AbortController`,贯通截图、检测、OCR、overlay 更新和 UIA enrichment;第三方模型不能中止时,至少在取消后丢弃结果、禁止更新 UI,并限制并发任务数。OCR 结果不应成为确认回填的无限等待条件:设定独立预算,超时允许以 AI 框 + UIA 结果回填,并将 OCR 标记为未完成。
**验收:**分别在截图、模型加载、检测、OCR、UIA enrichment 阶段取消;断言取消后不再更新 UI、不再启动后续步骤、overlay/hotkey/临时图片均清理;OCR 超时仍能按明确降级策略完成或失败。
### 12.6 P2:浏览器选择器属性读取路径存在二次解析偏差
`getText(selector, attribute)` 先经 `locate()` 成功,再在属性分支中直接 `document.querySelector(spec.value)`。即使 locate 未来补齐 XPath / Shadow DOM,属性分支仍会绕过统一解析器。应与 12.1 一并修复,不要单独打补丁。
### 12.7 当前测试证据不足:浏览器底层实现没有被集成测试覆盖
**源码核验:**当前 ZIP 中存在 `src/main/capabilities/browser*.test.ts`,但相关测试主要验证能力 handler 向 `BrowserLike` 转发参数及 mock 行为;未发现针对 `src/main/browser/session.ts` / `packages/rpa-core/src/browser/transport.ts` 的真实 DOM / CDP transport 单元测试。`browser-common.test.ts` 对 XPath 的测试验证的是把 `xpath://button` 传给 mock 的 `getElement`,并不验证 BrowserSession 能否解析 XPath。
**建议新增测试层:**
1. `selector-resolver.test.ts`:纯 DOM fixture,统一覆盖 CSS/XPath/Text/ARIA/Shadow DOM/iframe。
2. `cdp-transport.test.ts`:mock WebSocket,覆盖乱序响应、超时、close、断线和 pending 清理。
3. `browser-session.integration.test.ts`:使用真实 Chromium/Electron fixture 验证拾取 selector 到 click/type 的端到端一致性。
4. `browser-multitab.test.ts`:多 target 切换、前台页识别、页面导航与 target 生命周期。
5. `desktop-picker-cancel.test.ts`:各阶段取消、OCR 超时、overlay/hotkey/temp file 清理。
### 12.8 本轮深挖结论与修复顺序
| 顺序 | 事项 | 等级 | 建议处理 |
|---|---|---|---|
| 1 | 统一 selector resolver,修复 XPath / Shadow DOM 的 locate 执行不一致 | P1 | 先修,直接影响浏览器自动化成功率 |
| 2 | CDP request timeout、AbortSignal、pending 清理 | P1 | 先修,避免浏览器节点永久挂起 |
| 3 | iframe/frameId 支持边界与实现 | P1 | 先明确 MVP 支持范围,再实现 |
| 4 | 修正前台标签语义,不能把 pages[0] 当 active | P1 | 多标签演示前修复 |
| 5 | AI 拾取取消贯穿检测/OCR/资源清理 | P1 | 桌面拾取稳定性验收前修复 |
| 6 | 增加 BrowserSession/CDP/Picker 真实集成测试 | P1 | 修复后必须补证据 |
**本轮仍未执行:**Vitest、TypeScript typecheck、Electron build、真实 Edge/Chrome CDP 测试、Windows UIA/Office/DPI 实机测试。上述是源码级缺陷/风险定位,不等于已经在用户机器上复现。

View File

@ -0,0 +1,66 @@
# 迁入指南 V1 → V2 差异清单(含审查报告修正记录)
> 配套文档:`docs/rpa-core-migration-guide-v2.md`(V2 正文)。
> 本文记录三件事:① V1 各断言在真实源码下的判定;② 审查报告(docs/RPA-Review-Full.md)与真实源码不一致处的修正及理由;③ V2 新增内容清单。
> 核查基线:2026-11-06 双侧真实工作区源码。
---
## 一、V1(rpa-core-migration-guide.md)逐项判定
分类口径:**保留** / **需要修改** / **过时或不再适用** / **尚未实现(V1 写成了现状或反之)**。
| # | V1 断言/内容 | 判定 | 真实源码依据(2026-11-06) |
|---|---|---|---|
| 1 | 包现状:719 能力、34 域、宿主桥 4 方法、五域 Driver | **保留** | 实测 capabilityCount()=719;DOMAINS/PLATFORM_GROUPS 34 域;host-bridge.ts 仍 4 方法+SubflowSource 2 方法 |
| 2 | "完全不依赖 Electron,check:deps 谁加就报错" | **过时(不实)** | vision.ts:18,66,432 + cv-vision.ts:51,213 共 5 处动态 `import("electron")`;check-deps.mjs:20 正则 `import\s+` 对 `import("electron")` 零匹配(实测),护栏从未能拦住 |
| 3 | "npm run rpa-cli 证明能独立跑" | **需要修改** | rpa-cli.ts:25 仍有副作用全量 import、:53-59 全局 getHandler 直调,只能证明能力可跑,不能证明注入/隔离/无 Electron |
| 4 | 第 -1 步:"对面 7 个契约包每个只有 1-9 个声明文件,都是空壳" | **过时(核心叙事失效)** | workflow-engine 25 文件完整实现;capability-gateway 321 行实现;browser-plugin 已接 playwright-core(358 行 Runner);windows-plugin 新增 native-observer.ts(292 行);runtime-governance 有 readiness 投影;windows-runner 17 文件(C# FlaUI observe-only)。仍空壳的只剩 computer-control Runner 与 WindowsRunner.click/type |
| 5 | "迁入本质 = 拿 rpa-core 实现去填对面的契约" | **需要修改** | 改为"接底座 + 让路":不重复造状态机/浏览器/UIA 观察,RPA 补的是执行半边(WindowsRunner.click/type、CDP 接管场景)与 719 能力规模 |
| 6 | 第 0 步作业 #2"重新生成 catalog 让 platformTag 生效(当时没生效)" | **已完成(V1 写成待办)** | 2026-11-05 已重生成:win32=687 / null=326;残留 3 个数字 1 脏值待清洗 |
| 7 | 第 0 步作业 #1 Bun 冒烟 | **尚未实现** | bun 未安装,未跑过任何 Bun test |
| 8 | 第 0 步作业 #3 注册方式决策 | **需要修改** | 并入 ADR-R1(全局表 vs engine-scoped);rpa-cli 仍用旧方式 |
| 9 | 第 1 步:搬包位置 `code/backend/packages/rpa-core`、改名 `@agent-worker/rpa-core`、git subtree、demo 冻结 | **保留** | backend package.json workspaces 含 `packages/*`、全包 `@agent-worker/*` 前缀、`workspace:*` 源码消费——核实无误 |
| 10 | 第 2 步 A:capability-gateway"对照后改,不自建 allowlist" | **需要修改** | gateway 已实现(authorize/execute/租约 60s/幂等/高影响审批),工作是写 adapter 而非"对照设计" |
| 11 | 第 2 步 A:workflow-engine"状态模型对齐" | **需要修改** | 对面状态机已实现并稳定(含 waiting_approval/waiting_user/unknown 态);RPA 侧缺 waiting_user/unknown;集成形态二选一(ADR-R2) |
| 12 | 第 2 步 A:computer-control/windows-plugin "五域 driver 与契约对照" | **需要修改** | 契约依旧(Runner 无实现),但出现新硬约束:对面安全模型禁止任意 PowerShell(native-observer.ts 约束注释 + *.exe 白名单)→ ADR-R3 |
| 13 | 第 2 步 B:内置浏览器"最大变数,对面还没定型" | **过时(变数已收敛)** | PlaywrightBrowserRunner 已实现(白名单三重检+证据);变数变为"双路径取舍"(ADR-R4) |
| 14 | 第 2 步 B:宿主桥 4 方法 + SubflowSource 2 方法注入 | **保留**(落点补充) | 对面 desktop 主进程无现成宿主桥,但有等价物(identity-login-window/execution-context/zoom.ts);新增 ScreenCaptureHost 注入面(vision 迁移所需) |
| 15 | 第 2 步 B:"AI 能力接对面模型网关" | **保留并具体化** | model-gateway 已实现(stream + ModelControlPlane 每次调用授权);RPA ai-service 仍读 HostBridge.loadAiConfig,改造点明确 |
| 16 | 第 3 步 7 条验收 | **需要修改** | 扩充为 10 条总验收(加 gateway 零调用、取消传播、双 Engine 隔离、Graph Validator、纯 Node 加载) |
| 17 | 三端计划"搬家前 5 件小事" | **已完成(V1 写成待办)** | ① PLATFORM_GROUPS 单一源+守卫测试 ✅ ② 驱动自动选+platform_unsupported ✅ ③ COM 分组核查 ✅ ④ 5 个测试补 skipIf ✅ ⑤ driver/platform.ts 收拢 ✅(2026-11-05 落地) |
| 18 | 三端计划"搬家之后按平台写驱动" | **保留**(补约束) | 驱动实现统一走 ADR-R3 原生通道;Linux 容器不能跑桌面 Runner(对面 README 约束) |
| 19 | 附 10 决策 #2"保留全局注册表"、#9"注册双入口过渡" | **需要修改(重开 ADR)** | 与对面多租户/插件模型冲突,重开 ADR-R1;其余 8 个决策维持 |
| 20 | "浏览器:外部浏览器(CDP)进包,内置浏览器留宿主"(决策 5) | **保留**(补 ADR) | 仍成立,但需 ADR-R4 明确与 Playwright 的关系 |
## 二、审查报告(RPA-Review-Full.md)与真实源码不一致的修正
以真实源码为准,逐条记录修正理由:
| # | 审查报告断言 | 真实源码(2026-11-06) | 修正理由 |
|---|---|---|---|
| 1 | §3 P1-NEW-03 / 附录 P0-03:Engine 完全无注入,`driver` 固定 `new WindowsDriver()` | **部分过时**:driver 已改惰性 getter,非 win32 抛 platform_unsupported(engine.ts:47-61 + error-codes.ts:43 + engine-driver.test.ts) | 报告基于 11-06 前的 ZIP 快照;此后落地了"驱动按平台自动选择"。但"createEngine() 无注入参数"这一半仍属实 |
| 2 | §8.3 / 附录 P1-01:catalog 1016 项里 1013 项 platformTag=null、3 项数字 1,"平台标记从未生效" | **部分过时**:现为 win32=687 / null=326 / 数字 1=3 | PLATFORM_GROUPS 单一源落地后重生成过 catalog。"3 个数字 1 脏值透传(build-catalog.mjs:246 对 truthy 不清洗)"仍属实,保留为待办 |
| 3 | §12.4:"跟随前台标签页无条件取 pages[0]" | **需限定范围**:followForegroundTab 本身仍取 pages[0](session.ts:211),但 withElementOnce/locateOnce 已改两段式(当前页优先、失败才跟随,:296-307/:518-523);evaluate 默认 follow=true 仍会先切 | 该缺陷的暴露面已从"所有元素操作"缩小到"跟随函数本身+evaluate 默认路径",严重度下调,但未根除 |
| 4 | §6 P0 行"Engine 无 Driver 注入;能力使用 defaultEngine" | **属实且更严重**:desktop.ts:9 在模块加载时捕获 `defaultEngine.driver`,非 win32 上 import 即抛错(比报告写的"使用时才碰到"更早暴露) | 复核确认,问题加重一档 |
| 5 | §3 P1-NEW-02:包依赖缺 @napi-rs/canvas/pdfjs-dist/@cantoo/pdf-lib | **属实(复核确认)**:rpa-core/package.json:17-32 仍缺这 3 项,main/exports 直指 src | 无修正,维持 P1 |
| 6 | §12.1:locate 与 withElement 对 xpath/Shadow DOM 支持不一致 | **属实(复核确认)**:locateOnce :465-517 仅 text/aria/else 三分支 | 无修正,维持 P1 |
| 7 | §12.3:CDP send() 无单命令超时/AbortSignal | **属实(复核确认)**:transport.ts:96-118 | 无修正,维持 P1 |
| 8 | 附录 P2-04:AI 请求无调用方 signal | **属实(复核确认)**:ChatCompletionOptions(ai-service.ts:39-48)无 signal 字段 | 无修正,维持 P2 |
| 9 | §12.5:桌面拾取无 AbortSignal | **属实(复核确认)**:desktop-pick.ts 全文零 signal 命中(取消是热键+轮询自有机制) | 无修正,维持 P1 |
| 10 | §2.2 "尚未完成 Agent-Employee 真实契约的编译级对接;当前没有 Agent-Employee 目标仓库真实源码作为本轮输入" | **已过时**:本机已有 D:/code/Agent-Employee 真实工作区,V2 已完成逐接口核查 | V2 的核查基线即基于该仓库 |
| 11 | 附录 §8 ADR 表"Driver 契约:RPA Driver 是否直接实现 computer-control?建议对齐宿主契约" | **升级为阻塞项**:对面的安全约束(禁任意 PowerShell、*.exe 白名单)与 RPA 驱动实现方式正面冲突,不是"对齐一下"而是二选一(V2 ADR-R3) | native-observer.ts 设计约束注释明示"不执行任意 PowerShell",为 V1 快照中没有的新事实 |
| 12 | 报告整体:审查对象是资料库 ZIP 快照(无 node_modules、未装依赖) | **V2 基线升级**:真实工作区 + 依赖已装 + 可实测(正则盲区构造用例、catalog python 统计、tsx 实测 capabilityCount=719) | 结论可信度提升;仍保留"未做实机 E2E/联调"的边界 |
## 三、V2 相对 V1 的新增内容
1. **双侧事实清单**(V2 §2/§3):24 项 RPA 侧 + 12 项宿主侧,全部带 `文件:行号` 证据与状态标记——V1 无此层。
2. **宿主侧巨变披露**:plugin-registry/plugin-worker(对面真实插件宿主)、model-gateway、Java 控制面(Spring Boot 454 文件、OpenAPI+Outbox、端口分工)、evidence-store——V1 完全未提。
3. **6 项 ADR + 3 项 Q 待确认**(V2 §10),其中 R3(桌面安全模型)、R6(部署拓扑)为 V1 未识别的新阻塞项。
4. **逐接入点七要素详单**(V2 §7,A1-A10):RPA 位置/AE 位置/兼容性/问题/改哪侧/方案/验收——V1 只有粗粒度对齐表。
5. **模块对应关系表**(V2 §6):双侧模块一一对应 + 关系 + 现状。
6. **分阶段实施计划**(V2 §8):0-6 阶段每阶段带硬验收门;新增"阶段 0 事实确认会"。
7. **风险清单 10 项**(V2 §9):新增 R1 安全模型冲突、R2 部署拓扑、R10 底座演进过期风险。
8. **能力治理映射方案**:719 能力 ↔ PermissionScope 13 项的映射生成策略(脚本从 catalog 生成 + 高风险域人工审)——V1 只说"不自建 allowlist"。
9. **宿主桥注入面扩容预案**:4 方法 → +ScreenCaptureHost/DisplayCoordinateService(vision 迁移必需),V1 的 4 方法清单不再完整。
10. **明确"未实现/设计中"口径**:computer-control Runner、WindowsRunner.click/type、MacDriver/LinuxDriver、Graph Validator、bun 冒烟、ScreenCaptureHost 全部标为未实现,防止把计划写成现状(用户红线 #7)。

View File

@ -0,0 +1,358 @@
# Agent-Employee RPA 迁入指南 V2(基于双侧真实源码核查)
> 文档性质:对 `docs/rpa-core-migration-guide.md`(V1)的全面修订版。
> 核查基线:2026-11-06,双侧**真实工作区源码**(非快照 ZIP、非审查报告转述)——
> RPA 侧 `D:/code/PRA-only-demo`(main,工作区干净)、宿主侧 `D:/code/Agent-Employee`。
> 与 V1 的逐条差异及审查报告修正记录见 `docs/rpa-core-migration-guide-v2-diff.md`。
> 状态标记约定:**已实现**(代码在库)/ **部分实现** / **仅契约**(有类型无实现)/ **设计中** / **未实现** / **待确认**(不允许猜测)。
---
## 0. V2 核心结论(先读这个)
1. **宿主底座已大变,V1 的"空壳契约"叙事大部分失效。** V1 说对面 7 个契约包每个只有 1-9 个声明文件、windows-runner 是 0 源文件空壳。实测(2026-11-06):7 个包中 5 个已有实质实现(workflow-engine 25 文件、capability-gateway 321 行、browser-plugin 已接 Playwright、windows-plugin 新增原生观察适配器、runtime-governance 有 readiness 投影),windows-runner 有真实 C# 原生实现(.NET 10 + FlaUI UIA3,observe-only)。**仍无实现的只剩两处:computer-control 的 Runner、windows-plugin 的 WindowsRunner.click/type。**
→ 迁入的本质从 V1 的"拿实现去填契约"变为**"接底座 + 给已有实现让路"**:不重复造 workflow 状态机、不重复造 Playwright 浏览器、不重复造 UIA 观察。
2. **RPA 侧此轮整改完成了一部分**:平台标记单一源(PLATFORM_GROUPS 34 域 + 守卫测试)、驱动按平台自动选择(platform_unsupported)、catalog platformTag 687 项首次生效、5 个真桌面测试补平台守卫、`driver/platform.ts` 收拢。**但接入前 P0 大部分未解决**:`createEngine()` 仍无注入参数、Registry 仍是进程级全局、超时不中止底层操作、取消被标为 failed、5 处动态 `import("electron")` 残留、包依赖缺 3 项。
3. **迁入位置与名称维持 V1 结论**:`code/backend/packages/rpa-core` → `@agent-worker/rpa-core`,backend workspace(bun@1.4.3,workspaces 含 `packages/*`)放进去即生效,backend 全家统一 `@agent-worker/*` 前缀、`workspace:*` 源码消费。
4. **两条硬边界不变且加严**(用户红线):
- 所有带副作用的能力必须经 capability-gateway(授权→租约→幂等→执行→审计),**不得绕行**;
- rpa-core **不得依赖 Electron 主进程**(当前仍有 5 处动态 import,是接入阻塞项)。
5. **6 项 ADR 已拍板 5 项(2026-11-06)**,仅 R2(集成形态)带问题清单去对面对评审,详见 §10。已定基调:
- **R3 桌面执行**:C# 路线——五域执行逻辑移植进 windows-runner 原生进程,PowerShell 通道仅限本 demo。
- **R6 部署拓扑**:按能力域拆分——纯 Node 域进 backend plugin-worker,桌面/视觉/弹窗域装用户电脑 Electron 宿主,同一包按分组加载。
- **R1 能力清单**:全局一份,权限隔离全交对面网关按租户管。
- **R4 浏览器**:按场景分工——RPA 场景用咱们 CDP 接管(复用登录态/真实页面),对面 Playwright 留给 AI 智能体;白名单与证据共用对面一套。
- **R5 消费方式**:源码 git subtree 并入对面仓库统一编译,Bun 锁 1.4.3。
---
## 1. 核查方法与证据基线
- 双侧源码逐文件核查(两个独立探查通道 + 关键点人工复核),关键结论均带 `文件:行号` 证据。
- 实测过的事实:`import("electron")` 逃过 check-deps 正则(构造用例 0 匹配);catalog.json platformTag 分布(null=326 / win32=687 / 数字 1=3);capabilityCount()=719;对面 capability-gateway 租约 TTL=60s、幂等表上限 4096。
- 未验证(不写"通过"):Windows 实机 E2E、Bun 环境 test、对面真实链路联调。
- 审查报告(`docs/RPA-Review-Full.md`)与真实源码不一致处,以源码为准并记录修正理由(见 diff 文档第 2 节)。
---
## 2. RPA 侧现状事实清单(2026-11-06 实测)
| # | 事项 | 状态 | 证据 |
|---|---|---|---|
| 1 | Driver 惰性 getter + 非 win32 抛 `platform_unsupported` | 已实现 | engine.ts:47-61;error-codes.ts:43;engine-driver.test.ts |
| 2 | **Driver 可注入** | **未实现** | `createEngine()` 无参数(engine.ts:64-66);desktop.ts:9 等在模块加载时捕获 `defaultEngine.driver` |
| 3 | Registry 全局 Map、last-wins、runtime 读全局 getHandler | 属实(未改) | registry.ts:36-45;runtime.ts:260 |
| 4 | PLATFORM_GROUPS 单一源(34 域全 win32)+ buildDomainPlatformMap 真实实现 + 守卫测试 | 已实现 | register-all.ts:16-50,98-127;platform-groups.test.ts |
| 5 | withTimeout 监听 signal 但无 AbortController——超时不中止底层 | 属实(未修) | runtime.ts:33-56 |
| 6 | NodeState 类型含 cancelled,但 executeNode catch 把取消标成 failed | 属实(未修) | types.ts:142;runtime.ts:334-339 |
| 7 | 重试上限 100、黑名单式 isRetryable | 属实(未修) | runtime.ts:58-60,276 |
| 8 | Graph Validator | 未实现 | 全仓无 validateFlowGraph |
| 9 | 动态 `import("electron")` 5 处(vision.ts×3、cv-vision.ts×2) | 属实(未修) | vision.ts:18,66,432;cv-vision.ts:51,213 |
| 10 | check-deps.mjs 对 `import("electron")` 零匹配(盲区实测) | 属实 | check-deps.mjs:20 正则要求 `import\s+` |
| 11 | 包依赖缺 3 项:@napi-rs/canvas、pdfjs-dist、@cantoo/pdf-lib | 属实(未修) | rpa-core/package.json:17-32 对比 pdf.ts/vision.ts import |
| 12 | main/exports 直指 src 源码、private | 属实(决策项) | rpa-core/package.json:4-11 |
| 13 | rpa-cli 副作用全量 import + 全局 getHandler | 属实(未改) | scripts/rpa-cli.ts:25,53-59 |
| 14 | CDP transport 单命令无超时/无 AbortSignal/pending 仅断连清理 | 属实(未修) | transport.ts:96-118 |
| 15 | 两段式元素定位(当前页优先,失败才跟前台)| 已实现 | session.ts:296-307,518-523 |
| 16 | locate() 不支持 xpath / Shadow DOM(与 withElement() 不一致)| 属实(未修) | session.ts:465-517 对比 252-287 |
| 17 | HostBridge/SubflowSource 模块级单例 | 属实(未改) | host-bridge.ts:16-24,61-69 |
| 18 | AI ChatCompletionOptions 无 signal 参数 | 属实(未修) | ai-service.ts:39-48 |
| 19 | 桌面拾取无 AbortSignal(热键+轮询+deadline 自有机制)| 属实(未修) | desktop-pick.ts 全文零 signal 命中 |
| 20 | catalog platformTag:win32=687 / null=326 / **数字 1=3(脏值未清洗)** | 部分实现 | build-catalog.mjs:245-246 对 truthy 旧值透传 |
| 21 | catalog overrides(fields/removeFields/addFields) | 已实现 | catalog-overrides.mjs:6-24 |
| 22 | 能力 719 个 / 目录组件 1016(vendor 997 + native 16 + trigger 3)/ fields 4299 / 34 域 | 实测 | platform-groups.test.ts;catalog.json counts |
| 23 | 平台守卫测试 5 文件 + driver/platform.ts 唯一入口 | 已实现 | capabilities/*.test.ts;driver/platform.ts |
| 24 | Driver 五域方法均无 AbortSignal 参数 | 属实(未修) | driver/types.ts:87-119 |
---
## 3. 宿主侧(Agent-Employee)现状事实清单
| 契约包/服务 | V1 描述 | 2026-11-06 实测 | 状态 |
|---|---|---|---|
| workflow-engine | 9 个声明文件,空壳 | **25 文件完整实现**:WorkflowEngine 接口(create/start/beginStep/executeStep(runner)/completeStep/pause/resume/cancel/requestApproval/decideApproval)、状态机迁移表、Memory+PostgreSQL 双存储、recovery、REST API、worker(并发上限/取消/排空) | 已实现 |
| capability-gateway | 空壳契约 | **321 行完整实现**:`authorize(ref, subjectId, policy) → CapabilityLease`(TTL 60s)、`execute(lease, runner)`、幂等键预占/结算(上限 4096)、高影响审批精确 actionId 放行、结果 leaseId/actionId 回显校验 | 已实现 |
| browser-plugin | 纯契约 | **已接 playwright-core ^1.64.1**:PlaywrightBrowserRunner 358 行(navigate/click/type/snapshot),域名白名单三重复检(导航前/落点后/动作前),证据 SHA-256 落盘;定位仅 css/aria/text(无 xpath) | 已实现 |
| windows-plugin | 纯契约 | 契约 + **新增 native-observer.ts(292 行受信宿主适配器)**:spawn C# 观察进程、证据 realpath/SHA-256/PNG 魔数校验;`WindowsRunner{click;type;readText;snapshot}` 接口仍无实现;manifest `rpa.windows`:type=highImpact,全部 idempotencyRequired | 部分实现 |
| computer-control | 纯契约 | **未变**:纯类型 + 3 纯函数(selectPreferredTarget 降级链、assertComputerActionAllowed、assertComputerResultEvidence);5 种 target kind、6 种 action、CoordinateSpace(DPI/缩放契约已就绪) | 仅契约 |
| runtime-governance | 空壳 | readiness 投影实现(readiness.ts 117 行),无插件生命周期契约 | 部分实现 |
| plugin-contract | 纯类型 | 类型底座 + `assertPluginManifest` 校验实现;**PermissionScope 13 项**(browser.domain/storage、desktop.window/input/process、filesystem.workspace/download、network.http、office.document、messaging.send/read、media.generate、monitoring.capture);CapabilityLease/ActionResult/CapabilityRef(`pluginId:capabilityId`) | 部分实现 |
| services/windows-runner | 0 源文件空壳 | **17 文件真实 C# 实现**(.NET 10 + FlaUI UIA3 + PrintWindow,stdin/stdout JSON 协议,**只认 observe**,click/type 未实现);TS 宿主适配器校验证据 | 部分实现(观察半边) |
| plugin-registry / plugin-worker | (V1 未提) | registry=清单登记+版本链校验(无动态 loader);plugin-worker=常驻执行进程,PG LISTEN/NOTIFY 认领 workflow/session 动作、租约续期、跨进程取消传播、崩溃接管→unknown。**这就是对面当前的插件宿主** | 已实现 |
| model-gateway | (V1 未提) | 21 文件实现包:ModelGateway.stream 流式调用 + ModelControlPlane(每次物理调用向 Java 控制面重新授权,短授权 60s) | 已实现 |
| Java 控制面 | (V1 未提) | `code/backend/control-plane/`:Spring Boot 4.1.1 / Java 21 / 454 个 Java 文件 / 9 Maven 模块;与 TS 面经 OpenAPI + Outbox + 短授权 grant 通信;端口 8887(Java)/8881(Admin)/8880(TS 执行面) | 已实现 |
| frontend/apps/desktop | Electron+SolidJS | 确认。**无 RPA 宿主桥**,但有等价物:identity-login-window(弹窗)、execution-context.ts(grant 桥)、zoom.ts(缩放);DPI/坐标契约在 computer-control CoordinateSpace 与 C# Protocol.cs | 部分实现 |
对面安全模型要点(直接影响 RPA 桌面路线):windows-plugin 策略校验只允许 `*.exe` 进程名、不允许路径;native-observer.ts 设计约束明言"不执行任意 PowerShell";桌面交互 Runner 留宿主、不进 Linux 容器。
---
## 4. 原指南(V1)内容分类
| V1 内容 | 分类 | 处理 |
|---|---|---|
| 包现状描述(719 能力、34 域、宿主桥 4 方法、Driver 五域) | **保留**(微调) | 仍准确;宿主桥维持 4 方法不变 |
| "完全不依赖 Electron、check:deps 天天检查" | **过时/不实** | 5 处动态 import 逃过护栏;改为"静态依赖已清、动态残留待清",护栏必须升级 |
| 第 -1 步对面契约包全部空壳的叙事 | **过时** | 按 §3 改写:5/7 已实现,"填契约"变"接底座" |
| 第 0 步三件作业(bun 冒烟 / catalog 重生成 / 注册方式决策) | **部分保留** | catalog 重生成已完成(platformTag 687 生效,3 个脏值待清洗);bun 冒烟仍未做;注册方式决策并入 ADR-R1 |
| 第 1 步搬包(git subtree → code/backend/packages/rpa-core、改名 @agent-worker/rpa-core、demo 侧不双活) | **保留** | 双侧命名与 workspace 布局核实无误 |
| 第 2 步 A 类契约对齐表 | **需要修改** | capability-gateway/workflow-engine 从"对照后改对面"变"按已实现接口写 adapter";新增 plugin-contract/registry/worker 三层对接 |
| 第 2 步 B 类宿主注入表(浏览器=最大变数) | **需要修改** | 浏览器变数已收敛:对面有 Playwright 实现;变数改为"双路径取舍"(ADR-R4) |
| 第 3 步 7 条验收 | **需要修改** | 扩充为 §9 验收标准(加 gateway 零调用、取消传播、双 Engine 隔离等) |
| 三端计划(PLATFORM_GROUPS 单一源、驱动自动选、分组核查、测试守卫、平台判断收拢) | **已完成,从"待办"改"现状"** | 五件小事全部落地;平台硬约束一节保留为长期规范 |
| 附:10 个关键决策 | **大部分保留** | 决策 2"保留全局注册表"与决策 9"双入口"需按 ADR-R1 重新确认;其余不变 |
---
## 5. 接入目标架构
```text
┌─ Agent-Employee(宿主:权威状态与治理的唯一来源)──────────────────────┐
│ │
│ Java 控制面 control-plane(:8887) ──OpenAPI/Outbox/短授权 grant──┐ │
│ │ │
│ TS 执行面 apps/api(:8880, Bun) │ │
│ ├─ workflow-engine ← Run/Step 权威状态机(PG 存储+recovery) │ │
│ ├─ capability-gateway← 授权→租约(60s)→幂等→执行→审计【唯一入口】│ │
│ ├─ plugin-registry ← 清单登记 + 版本链校验(无动态 loader) │ │
│ ├─ model-gateway ← AI 流式调用 + 每次调用控制面授权 │ │
│ └─ services/plugin-worker ← 常驻执行进程(LISTEN/NOTIFY 认领) │ │
│ ├─ browser-plugin → PlaywrightBrowserRunner(已实现) │ │
│ └─ windows-plugin → windows-runner(C# FlaUI, observe-only)│ │
│ │
│ frontend/apps/desktop(Electron+SolidJS,用户桌面宿主) │ │
│ └─ RPA 宿主桥实现落点(弹窗/DPI/AI 配置/子流程仓库) │ │
└────────────────────────────────────────────────────────────────┘
▲
│ CapabilityRunner(gateway 包皮,唯一执行通道)
│ Run/Step 事件回传(RPA 不建第二套状态)
┌─ RPA 迁入物(执行器与能力供给方)──────────────────────────────────────┐
│ @agent-worker/rpa-core(packages/rpa-core 迁入,Bun 源码消费) │
│ ├─ Flow Runtime(作为 workflow-engine 的执行引擎候选 / 步骤级 runner)│
│ ├─ 719 能力 handler(纯 Node 部分可在 plugin-worker 服务器侧跑; │
│ │ 桌面/视觉部分只能跑在用户桌面宿主进程——见 ADR-R6) │
│ ├─ WindowsDriver(补齐对面 WindowsRunner.click/type 执行缺口,ADR-R3)│
│ └─ CDP 外部浏览器会话(与 PlaywrightBrowserRunner 的取舍,ADR-R4) │
│ @agent-worker/rpa-adapter(新增薄适配层) │
│ ├─ CapabilityRunner:authorize/execute 包裹 → ref↔componentId 映射 │
│ ├─ RunResult/NodeState ↔ WorkflowStatus/StepStatus 映射表 │
│ └─ 宿主注入装配:HostBridge(4) + SubflowSource(2) + ScreenCaptureHost│
└────────────────────────────────────────────────────────────────────────┘
```
**权责原则(沿审查报告,逐条可执行):**
- workflow-engine 是 Run/Step 状态唯一权威;RPA Runtime 只回传事件,不落第二份终态。
- capability-gateway 是唯一授权入口;RPA 侧 `getHandler()` **不对 adapter 之外暴露**(rpa-cli 顺手重写以作表率)。
- computer-control 是桌面契约对齐对象;RPA 不新造并行的桌面抽象。
- 宿主服务一律注入,禁用模块级 setter 承载多会话状态(现状 HostBridge 单例是过渡,见 ADR-R1)。
---
## 6. 模块对应关系表
| RPA 侧(packages/rpa-core) | Agent-Employee 侧 | 关系 | 现状 |
|---|---|---|---|
| `engine/runtime.ts` 流程执行 | `workflow-engine` WorkflowEngine/StepRun | RPA=执行算法候选,AE=权威状态 | 集成形态待 ADR-R2 |
| `engine/registry.ts` 全局注册表 | `plugin-registry` + `capability-gateway` CapabilityRef | ref↔componentId 映射层(rpa-adapter) | 映射待建 |
| 719 能力 handler | `plugin-contract` CapabilityDefinition + PermissionScope 13 项 | 能力清单生成 + 权限映射 | 待建(脚本生成) |
| `RunContext.signal` | `CapabilityExecutionContext.signal` | 均 AbortSignal,直通 | 兼容 |
| `NodeState`(idle/running/succeeded/failed/skipped/cancelled) | `StepStatus`(+waiting_user/unknown/pending) | 映射表;RPA 缺 waiting_user/unknown | 需补 |
| `human-dialog` 审批弹窗 | `WorkflowApprovalRequest`/decideApproval | 人审节点挂宿主 approval 流 | 需 adapter |
| `driver/windows/*`(PowerShell 五域) | `windows-runner` C#(observe-only)+ `WindowsRunner{click,type}` 契约 | RPA 补执行半边;安全模型冲突 | ADR-R3 |
| `browser/*` CDP 会话(BrowserLike) | `browser-plugin` PlaywrightBrowserRunner | 双路径取舍 | ADR-R4 |
| `ai-service.ts` loadAiConfig | `model-gateway`(ModelGateway.stream + ModelControlPlane) | RPA AI 能力改走宿主网关 | 需改造 |
| `host-bridge.ts` 4 方法 | frontend desktop main 进程(弹窗/坐标/AI 配置等价物已有) | 参考实现照抄本仓库 src/main/index.ts | 落点明确 |
| `SubflowSource` 2 方法 | 对面流程存储 | 包一层 | 小 |
| vision/cv-vision 截图+DPI | `computer-control` CoordinateSpace + C# PrintWindow | 宿主注入 ScreenCaptureHost | 待建 |
| 运行证据/截图 | `evidence-store`(S3) + `monitoring.capture` 权限 | 证据统一落对面 | 待接 |
| `check-deps.mjs` 依赖护栏 | backend 包边界 | 保留并升级(动态 import) | 需增强 |
---
## 7. 逐接入点详单
每项按固定七要素:RPA 位置 / AE 位置 / 兼容性 / 问题 / 改哪侧 / 方案 / 验收。
### A1 Engine 与 Driver 注入
- **RPA 位置**:`packages/rpa-core/src/engine/engine.ts:47-66`(惰性 getter + `createEngine()` 无参);`capabilities/desktop.ts:9`、`common-final.ts:11`、`system-extended.ts`、`window-extended.ts`、`office.ts:15`、`ftp.ts`、`ocr-caps.ts:11` 捕获 `defaultEngine.*`(其中 desktop.ts:9 在**模块加载时**取 `.driver`,非 win32 下 import 即抛错)。
- **AE 位置**:对面不关心 driver 细节;对齐契约是 `computer-control` 的 `ComputerRunner{observe;execute}`(computer-control/src/index.ts:99-102)与 `windows-plugin` 的 `WindowsRunner`。
- **兼容性**:部分。平台错误语义(platform_unsupported)已就绪;注入面缺失。
- **问题**:宿主无法注入测试替身或 computer-control 适配器;能力文件模块级捕获引擎状态(excelHandles/ftpClient/ocrInstance/driver),多会话共享。
- **改哪侧**:RPA。
- **方案**:`createEngine(options?: { driver?, hostBridge?, subflowSource?, registry? })`,全部可选、缺省保持现行为;能力文件改为经 RunContext/engine 参数取 driver 与资源表(模块级捕获改为首用时向 engine 取);幂等句柄表(excelHandles/ftpClient/ocrInstance)迁入 engine 实例字段(部分已是)。
- **验收**:FakeDriver 注入测试覆盖五域;两个 Engine 各注入不同 FakeDriver 并行执行互不串用;非 win32 上 import 全部 34 域不抛错、调用桌面能力时才抛 platform_unsupported。
### A2 Registry 注册机制与实例隔离
- **RPA 位置**:`engine/registry.ts:36-45`(模块级 Map、last-wins、meta 可残留旧值);`runtime.ts:260`(全局 getHandler);`register-all.ts`(34 域 DOMAINS + PLATFORM_GROUPS + buildDomainPlatformMap)。
- **AE 位置**:`plugin-registry`(清单登记+版本链,无动态 loader)、`capability-gateway` CapabilityRef(`pluginId:capabilityId` + version)。
- **兼容性**:结构性错位。对面一切能力调用都带 CapabilityRef 与版本;RPA 是字符串 id 全局表。
- **问题**:进程级注册在对面"多租户 + plugin-worker 常驻进程"模型下无法按租户隔离;componentId 与 CapabilityRef 无映射;同 id 覆盖无审计。
- **改哪侧**:短期 RPA 侧固化 ADR + 新建 adapter 映射;长期 RPA 侧 engine-scoped registry。
- **方案**:①ADR-R1 先定部署模型(单后端单能力集 → 全局表合法,把 `registerAll` 文档语义改为"加载到进程注册表",不假装实例隔离);②rpa-adapter 维护 `componentId ↔ (pluginId="rpa", capabilityId=componentId, version=catalog 版本)` 映射,由 catalog.json 生成并做完整性测试;③重复注册策略定为"显式覆盖 + 输出 warn 审计行"。
- **验收**:映射表覆盖 719 能力(或明确排除清单);gateway 侧按 ref 解析到正确 handler;重复注册产生可审计日志。
### A3 Runtime 执行、超时、取消、重试
- **RPA 位置**:`runtime.ts:33-56`(withTimeout 只 reject 外层)、`runtime.ts:334-339`(取消标 failed)、`runtime.ts:58-60,276`(isRetryable 黑名单、retryCount≤100)、`driver/types.ts:87-119`(五域方法无 signal)。
- **AE 位置**:`workflow-engine` StepRun.attempt/leaseEpoch/idempotencyKey、`workflow-worker` 跨进程取消传播与崩溃接管→unknown、`capability-gateway` execute 的 `ctx.signal?`。
- **兼容性**:不兼容。对面以幂等键 + 租约纪元管理副作用安全;RPA 超时/取消/重试语义与它冲突。
- **问题**:超时后底层 PowerShell/HTTP/CDP 仍执行(重复副作用);取消状态不一致;非幂等能力可被重试 100 次。**这是接入的第一阻塞项。**
- **改哪侧**:RPA。
- **方案**:①节点尝试创建独立 AbortController,合并 Run 级 signal 与节点 timeout,贯通 handler→driver→PowerShell 子进程/HTTP/CDP(driver 五域方法加可选 `signal` 参数);②executeNode catch 优先映射 cancelled → `state.status="cancelled"`,不走 skipOnError/error route/retry;③重试改元数据驱动:CapabilityMeta 增 `idempotent`/`sideEffect`/`retryPolicy`,非幂等默认 0 次重试;④新增 `validateFlowGraph`(ID 唯一、边合法、入口/出口基数、环、悬空边)。
- **验收**:构造 5 秒 Fake Handler + 100ms 超时,断言 handler 收到 abort 并停止、无并发重入;执行中取消后 Run 与 NodeState 均为 cancelled;对 File 写入/点击类能力验证"超时不自动重试";非法图执行前得到稳定错误码。
### A4 Capability Gateway(能力注册与权限治理)
- **RPA 位置**:`registry.ts`(handler + meta 无权限字段);`resources/catalog.json`(仅 `risk: low|high`)。
- **AE 位置**:`capability-gateway/src/index.ts:75-252`(已实现 authorize/execute/租约 60s/幂等/高影响审批);`plugin-contract` PermissionScope 13 项。
- **兼容性**:接口契合度高——`CapabilityRunner = (input, ctx{lease,capability,signal?}) => Promise<ActionResult>`,RPA handler `(ctx, node, args, component)` 包一层即可;ActionResult 需回显 leaseId/actionId(adapter 统一填)。
- **问题**:①719 能力 ↔ 13 项 PermissionScope 无映射;②CapabilityMeta 无 sideEffect/idempotent/permission/approvalRequired 字段;③存在绕行面(rpa-cli、子流程直接 getHandler)。
- **改哪侧**:新建 adapter(AE 仓库内包)+ RPA 补元数据。
- **方案**:①rpa-adapter 实现 CapabilityRunner:gateway.execute(lease, runner) → ref→componentId → 构造 RunContext(signal 直通)→ handler → 结果包装 ActionResult;②权限映射表按 34 域 × PermissionScope 初稿生成(files→filesystem.workspace、http→network.http、desktop/windowExtended→desktop.input/window、utilityExtended→独立高影响 scope【待确认:现表无 process 执行域对应,需对面确认】、message→messaging.send);③高风险域标 highImpact 走审批(System_ExecuteCommand、键鼠、文件删除/覆盖、发消息);④meta 增治理字段(与对面 CapabilityDefinition 字段对齐)。
- **验收**:审批拒绝/租约过期/权限不在白名单时 handler **零调用**(gateway 已有测试语义,adapter 层加 719 全量冒烟:未经 authorize 不得触达 handler);审计可关联 Agent/Run/Capability/审批结果。
### A5 Workflow Engine 执行契约
- **RPA 位置**:`engine/runtime.ts`(RunResult/NodeState)、`engine/types.ts:142`、human-dialog 审批弹窗、SubflowSource。
- **AE 位置**:`workflow-engine/src/index.ts:278-311`(WorkflowEngine 接口)、`workflow-runtime.ts`、`workflow-worker.ts`、`approval-policy.ts`;`executeStep(..., runner)` 的 runner 类型即 `Parameters<CapabilityGateway["execute"]>[1]`。
- **兼容性**:状态映射大体可行;RPA 缺 `waiting_user`/`unknown` 两态;对面 StepStatus 无 RPA 的 `idle`。
- **问题**:两套状态权威冲突风险;集成形态二选一未定(整体作为一个 capability vs 节点级映射为 step)。
- **改哪侧**:adapter + 双侧小改。
- **方案**:**推荐 MVP 方案 A**——整个 RPA 流程注册为**一个** capability(`rpa.flow.execute`),workflow-engine 的一个 step 调它,RunResult→ActionResult、节点级事件→event-bus 事件流、human-dialog→requestApproval 挂对面 approval 流、取消→signal 直通;方案 B(RPA 节点=step 1:1 映射)等方案 A 验收后按需评估。NodeState 增 `waiting_user`/`unknown` 两态(unknown 用于崩溃恢复对齐)。
- **验收**:状态映射契约测试(含 waiting_user/unknown/cancelled 全分支);一次含审批节点、一次执行中取消、一次进程崩溃恢复的端到端演练;对面 Run 状态终态与 RPA RunResult 永远一致。
### A6 Computer Control 与 Windows 桌面执行
- **RPA 位置**:`driver/windows/*`(PowerShell 封装五域)、`capabilities/desktop-uia.ts`、`element.ts`。
- **AE 位置**:`computer-control`(仅契约+3 纯函数)、`windows-plugin`(WindowsRunner{click;type;readText;snapshot} 无实现、native-observer.ts observe-only)、`services/windows-runner/native/*.cs`(FlaUI UIA3 observe-only)。
- **兼容性**:**安全模型冲突**——RPA 驱动 spawn 任意 PowerShell;对面白名单只许 `*.exe`、明文"不执行任意 PowerShell"、证据要求 SHA-256。
- **问题**:RPA 的执行半边(click/type)正是对面缺口,但实现方式过不了对面安全门。
- **改哪侧**:双侧(ADR-R3 已拍板:C# 扩展 actuate,见 §10)。
- **方案**:推荐——把 RPA WindowsDriver 的五域执行逻辑**移植进 windows-runner 的 C# 原生进程**( FlaUI/CGEvent 等价实现 + 现成协议加 actuate 操作),RPA 侧 driver 抽象保留、Windows 实现改为可指向"原生进程通道"或"本地 PowerShell 通道"(后者仅 demo 使用,明确标注不走宿主治理);策略校验/证据断言复用对面 `assertComputerActionAllowed`/`assertComputerResultEvidence`。
- **验收**:click/type 经策略校验(白名单进程/窗口、allowInput)+ 证据落盘;宿主拒绝时零副作用;同目标 observe→actuate→observe 闭环一致。
### A7 浏览器(Browser Plugin vs RPA CDP 会话)
- **RPA 位置**:`packages/rpa-core/src/browser/*`(transport/external,进包部分)+ `src/main/browser/session.ts`(接管/拾取,留守部分);已知缺陷:transport 单命令无超时(:96-118)、locate 不支持 xpath/Shadow DOM、evaluate 默认 follow=true。
- **AE 位置**:`browser-plugin`(PlaywrightBrowserRunner 已实现:白名单三重检、AbortSignal 透传、证据 SHA-256;定位 css/aria/text,无 xpath)。
- **兼容性**:双实现并行,语义不同(Playwright 自管浏览器 vs RPA 接管用户已开浏览器/复用登录态)。
- **问题**:重复投资;RPA CDP 链路自身有 P1 缺陷;对面定位器无 xpath(RPA 拾取器产出 xpath/css/aria/text 混合 selector)。
- **改哪侧**:ADR-R4 已拍板(按场景分工):RPA 场景走咱们 CDP 接管(主力),对面 Playwright 留给 AI 智能体场景。
- **方案**:①RPA 浏览器能力迁前先修 transport 单命令超时 + 统一 resolveElement(一次修复覆盖 locate/click/type/getText);②CDP 接管路线接对面 BrowserContextPolicy 白名单与证据体系(不自建白名单);③与对面确认 rpa.browser 插件清单承载咱们的 runner;④PlaywrightBrowserRunner 保持现状服务 AI 智能体场景,双方不重复投资。
- **验收**:同一定位用例集(css/aria/text/xpath)在咱们 CDP 路径全部通过;白名单拒绝 URL 时导航前即拦截(走对面策略对象);证据落 evidence-store。
### A8 平台适配(Windows / Linux / macOS)
- **RPA 位置**:`register-all.ts` PLATFORM_GROUPS(34 域全 win32)、`driver/platform.ts`、`engine/engine.ts:47-61`、5 个 skipIf 测试;catalog 687 win32 / 326 null / **3 个数字 1 脏值**。
- **AE 位置**:`plugin-contract` Platform 类型;windows-runner 明确"留在宿主、不进 Linux 容器"(README)。
- **兼容性**:兼容(两边都是 win32-first 务实路线)。
- **问题**:①catalog 3 个 `platformTag: 1` 脏值透传(build-catalog.mjs:246 对 truthy 不清洗);②对面 CapabilityDefinition 未见 platform 字段(**待确认**:插件清单如何表达平台限制)。
- **改哪侧**:RPA(清洗);对面(清单平台字段,待确认后)。
- **方案**:build-catalog 归一化——源值不在 `win32|darwin|linux|null` 白名单的一律重派生;三端扩展节奏沿用 V1 第三节(Linux xdotool/AT-SPI、Mac AX/CGEvent/TCC、Office 分端实现),驱动实现统一走 ADR-R3 的原生进程通道。
- **验收**:catalog 全量过 schema 校验(1016/1016);非 win32 上 Core 可加载、桌面能力明确报 platform_unsupported;Mac/Linux CI 跑纯逻辑子集。
### A9 Electron 依赖与 Core 独立性(含包闭包)
- **RPA 位置**:vision.ts:18,66,432、cv-vision.ts:51,213(动态 `import("electron")`);check-deps.mjs:20(正则盲区实测);rpa-core/package.json:17-32(缺 @napi-rs/canvas、pdfjs-dist、@cantoo/pdf-lib;main 直指 src、private)。
- **AE 位置**:backend 执行面是 Bun(无 Electron);Electron 只在 frontend/apps/desktop;截图/坐标对面等价物是 C# PrintWindow + CoordinateSpace 契约。
- **兼容性**:不兼容。视觉能力在对面 backend 进程必然失败。
- **问题**:Core 不是 Electron-free;护栏查不出动态 import;单包安装缺 3 个依赖。
- **改哪侧**:RPA。
- **方案**:①抽 `ScreenCaptureHost`/`DisplayCoordinateService` 接口并入 HostBridge 注入面(弹窗/DPI×2/AI 配置 4 方法 → 增至 6 方法,或独立 host services 包——实现期定,倾向并入 HostBridge 减少 API 面);vision/cv-vision 只依赖接口,宿主未注入时抛 `host_service_unavailable`;②check-deps 升级为覆盖 `import()`/`require()`/`createRequire()`(先补正则,中期 AST);③补齐 3 个依赖声明(pdf.js 与 canvas 为原生/大件,评估 optionalDependencies + 能力级降级);④消费方式锁定 **Bun 源码消费**(对面全家 workspace:* 源码依赖,与现状一致),锁 bun@1.4.3 做 CI 冒烟。
- **验收**:无可解析 electron 的纯净 Node/Bun 环境 Core 全量 import 成功;视觉能力未注入 Host 时返回 host_service_unavailable、注入 Fake Host 后可测;隔离目录仅按包声明装依赖跑通 PDF/OCR 代表能力。
### A10 插件生命周期、异常隔离与资源清理
- **RPA 位置**:无插件概念;资源清理散在能力内(PowerShell 子进程、ws、FTP/OCR 句柄);历史上有 catch{} 吞错模式(已部分修复)。
- **AE 位置**:`plugin-contract` PluginManifest(apiVersion/minCoreVersion/entrypoint/rollbackVersion)+ assertPluginManifest;`plugin-registry` 版本链校验;`plugin-worker`(崩溃接管→unknown、租约续期、跨进程取消)。
- **兼容性**:RPA 需要一张插件"外壳"才能进对面宿主。
- **问题**:719 能力不可能逐个成插件;RPA 无崩溃恢复语义(对面 unknown 态)。
- **改哪侧**:adapter 为主。
- **方案**:RPA 以**单一插件**接入(manifest id 建议 `rpa.core`,能力按域分批暴露为 CapabilityDefinition 清单,随 catalog 生成);异常隔离靠节点级 try/catch + AbortSignal(A3);资源清理建统一 finally 清单(子进程 kill、ws close、临时文件删除)并纳入取消路径;崩溃恢复复用对面 workflow-recovery(NodeState.unknown 对齐)。
- **验收**:杀掉 plugin-worker 进程后 Run 转入 unknown 并可恢复;取消后无残留子进程/连接/临时文件;manifest 通过 assertPluginManifest 与 registry 版本链。
---
## 8. 分阶段实施计划
> 原则:先 RPA 侧收口(便宜、不依赖对面),再垂直切片,再规模化。不为了架构统一搞大重构;每阶段有硬验收门。
| 阶段 | 内容 | 前置 | 验收门 |
|---|---|---|---|
| **0 事实确认**(双侧,1 次评审会) | 评审 R2(带方案 A/B 对比与三项待答问题)+ 3 项 Q;对面确认 CapabilityDefinition 平台字段、utilityExtended 对应 scope;同步已拍板 5 项结论(R1/R3/R4/R5/R6)给对面并预估 C# actuate 扩展量 | 无 | R2 与 Q 有书面结论 |
| **1 RPA 侧收口**(本仓库) | A3 四件(取消贯通/状态映射/重试元数据/Graph Validator)+ A1 注入参数 + A9(Electron 残留、check-deps 升级、依赖闭包)+ A2 的 ADR 固化 + catalog 脏值清洗 + rpa-cli 重写(显式注册、经 engine 取 handler) | 无 | 审查报告 §7 第一/二阶段清单全绿;纯 Node 环境 Core 全量加载 |
| **2 迁入与 adapter 骨架** | git subtree 迁包改名 @agent-worker/rpa-core;bun@1.4.3 test 冒烟(exceljs/pdf/PowerShell 编码);新建 @agent-worker/rpa-adapter(CapabilityRunner + ref 映射 + 状态映射表);demo 侧冻结 | 阶段 1 + ADR-R1/R2 | bun test 纯逻辑全绿;未经 gateway 不可达 handler;719 映射完整性测试 |
| **3 垂直切片** | 一个文件能力 + 一个 HTTP 能力走通:workflow-engine create/start → gateway authorize → adapter → rpa-core handler → 事件/证据回传 → cancel 中途取消;方案 A(rpa.flow.execute 整体注册) | 阶段 2 | 取消/审批/审计全链有证据;对面 Run 终态与 RunResult 一致 |
| **4 浏览器与桌面双 ADR 落地** | ADR-R4(已拍板):RPA 浏览器能力修完 transport/resolver,接对面白名单与证据体系后作为 RPA 场景主力 runner;ADR-R3:windows-runner C# 扩展 actuate(RPA 五域逻辑移植) | 阶段 3 | §7-A6/A7 验收条目 |
| **5 能力规模化** | 34 域按风险分批:files/data/http/ftp → office/excel/word(COM 仅 win32)→ desktop/utility 高影响(末批+审批);CapabilityDefinition 与权限映射随批生成 | 阶段 4 | 每批过 gateway 全量冒烟 + 审批矩阵抽查 |
| **6 平台扩展** | V1 第三节三端计划原样生效(Linux/macOS 驱动按 ADR-R3 原生通道) | 阶段 5 | 三平台 CI 矩阵;产品按 platformTag 置灰 |
**不搬的东西**(维持 V1 清单):界面渲染层、悬浮框/取色器、本地视觉 onnxruntime、流程存储、内置浏览器编排——由宿主提供或对面重做;其中"本地视觉"明确**不进 backend**(原生依赖 + 桌面截图依赖),留在宿主桌面进程。
---
## 9. 风险清单与总验收标准
### 风险清单
| # | 风险 | 等级 | 缓解 |
|---|---|---|---|
| R1 | ~~桌面安全模型冲突~~ → **已缓解(ADR-R3 拍板 C# 路线)**:残余风险转为 C# 移植工作量与工期 | 中 | 阶段 0 与对面确认 actuate 扩展量;RPA 五域逻辑先行整理成移植清单 |
| R2 | ~~部署拓扑错配~~ → **已缓解(ADR-R6 拍板按域拆分)**:残余风险为宿主进程加载子集的工程实现 | 中 | 阶段 2 按 PLATFORM_GROUPS 分组做宿主子集加载验证 |
| R3 | Registry 全局单例在多租户 backend 的隔离缺陷 | 高 | 短期按 ADR-R1 限定部署模型;长期 engine-scoped registry 列入 backlog |
| R4 | 浏览器双路径重复投资 | 低(已缓解) | R4 已拍板按场景分工(RPA=CDP 接管,AI 智能体=Playwright),白名单与证据共用一套,无重复投资 |
| R5 | 719 能力的治理映射与元数据补齐工作量失控 | 中 | 脚本从 catalog 生成初稿 + 人工只审高风险域;分批(阶段 5) |
| R6 | Bun 与 Node 行为差异(exceljs/pdf-lib/PowerShell 编码) | 中 | 阶段 2 bun 冒烟前置,差异清单化 |
| R7 | demo 侧与 backend 双活跃漂移 | 中 | git subtree + "迁移后 demo 冻结"纪律(V1 决策 3) |
| R8 | 两套状态机语义漂移(NodeState vs StepStatus) | 中 | 映射表 + 契约测试常驻 CI |
| R9 | catalog 源数据(shizai)上游已删,重生成受阻 | 低 | 已从对面历史恢复至 temp/catalog-source;计划快照入库 |
| R10 | 对面底座继续快速演进,本文行号/接口过期 | 中 | 本文标注核查日期;联调前对关键接口做增量 diff |
### 总验收标准(迁移完成的定义)
1. Bun@1.4.3 下包 typecheck/test 全绿(不以 Node 测试替代)。
2. FakeDriver/Host 注入可用,双 Engine 隔离行为符合 ADR-R1。
3. 全部已暴露能力经 capability-gateway:审批拒绝/租约过期/无权限时 handler 零调用,审计四元组(Agent/Run/Capability/审批)可追踪。
4. 取消/超时/重试测试证明:超时即中止底层、取消不进 error route、非幂等零自动重试、无并发重入。
5. Graph Validator 拒绝非法流程图于执行前。
6. 纯 Node/Bun 环境(无 electron 可解析)Core 全量加载;视觉路径 Host Mock 可测。
7. 隔离目录按包声明安装依赖,代表能力(文件/Excel/HTTP/PDF)可跑。
8. 垂直切片端到端演练通过:含一次审批、一次中途取消、一次进程崩溃恢复(unknown→恢复)。
9. Windows 10/11 实机回归:UIA/浏览器/Office/DPI/多屏/安装包资源 + 取消场景。
10. catalog 1016 项全过平台 schema 校验;能力平台范围与真实实现一致。
---
## 10. 决策与待确认清单
### 已拍板(2026-11-06,本方负责人确认)
| ADR | 问题 | 决定 |
|---|---|---|
| R1 | Registry 作用域:backend 是否多租户并存 | **全局一份能力清单**(菜单所有租户共用),权限隔离完全由 capability-gateway 按租户管;长期将清单挂到 engine 实例(已从对面源码确认 WorkflowIdentity 带 tenantId,但清单本身无需按租户复制) |
| R3 | 桌面执行安全模型:RPA PowerShell 驱动 vs 对面"受信 C# 原生进程"红线 | **C# 扩展 actuate**:把 RPA 五域执行逻辑移植进 windows-runner 原生进程;PowerShell 通道仅限本 demo,不进宿主 |
| R4 | 浏览器双路径 | **按场景分工**:RPA 场景走咱们的 CDP 接管路线(复用用户登录态/真实页面——RPA 核心价值);对面 PlaywrightBrowserRunner 留给 AI 智能体场景(无头自管浏览器)。约束:①咱们 CDP 路线必须接对面 BrowserContextPolicy 白名单与证据体系,不自建;②迁移前修完 transport 超时与定位器不统一两缺陷;③需对面在插件清单给咱们的 runner 备案(rpa.browser) |
| R5 | 包消费方式 | **源码并入 + 锁版本**:git subtree 进对面仓库统一编译,Bun 锁 1.4.3(与对面全家 workspace:* 源码消费现状一致) |
| R6 | 部署拓扑:rpa-core 各域跑在哪 | **按能力域拆分部署**:纯 Node 域→backend plugin-worker;桌面/视觉/弹窗域→用户电脑 Electron 宿主;同一包按 PLATFORM_GROUPS 分组加载,不维护两份代码 |
### 待对面对评审确认(不猜测)
| ADR | 问题 | 评审要点 |
|---|---|---|
| R2 | 集成形态:RPA 流程=单个 capability(方案 A)还是节点=step 映射(方案 B)? | 本方需对面补充信息后定:①对面产品上是否要求逐步审批/逐步可见(决定 A 是否够用);②对面 workflow-engine 的 step 粒度是否有上限或性能顾虑(719 能力×节点数映射成 step 的量级);③对面对 RPA 执行中途取消/暂停的交互预期。方案对比:A=对面一个 step 跑完一条 RPA 流程(工作量小、状态映射简单、对面只见"开始/结束"+事件流);B=每个节点一个 step(全程透明可逐步审批,工作量数倍、两套状态机对表复杂)。本方倾向 A 起步、协议预留节点级事件上报。 |
| Q# | 问题 | 评审要点 |
|---|---|---|
| Q1 | CapabilityDefinition 是否需要 platform 字段(对面清单未见) | 建议对面补字段或约定由 catalog platformTag 承载 |
| Q2 | utilityExtended(命令执行)对应哪个 PermissionScope | 13 项中 desktop.process 是候选,需对面确认语义(backend 无桌面,进程执行属哪域) |
| Q3 | human-dialog 弹窗宿主落点 | 已被 R6 顺带回答:用户电脑 Electron 宿主窗口;仅弹窗样式/交互细节与对面 UI 对齐 |
---
## 11. 本指南边界
- 全部结论基于 2026-11-06 双侧工作区源码静态核查 + 少量脚本实测(正则盲区、catalog 统计、capabilityCount);未执行 Windows 实机 E2E 与对面联调,未运行的测试不写成通过。
- 行号会随代码演进过期,以函数/接口名为准。
- 本轮只改文档,不改业务源码(用户指令);阶段 1 起的代码改动另行排期。

View File

@ -1,103 +0,0 @@
# 图像与OCR 能力问题检测报告
- **检测时间**:2026-10-30 10:00–10:10
- **检测人**:刘爱家(AI 辅助静态核对 + 真实运行验证)
- **检测范围**:图像与OCR 分组已接入的全部 10 项能力(Image 7 项 + OcrOffline 3 项)
- **检测方式**:
1. 静态核对——组件目录(catalog.json)字段定义 vs 实现代码(vision.ts / ocr-caps.ts)逐键比对
2. 真实运行——GDI 生成含中英文的测试图、真实截屏、模板匹配直测、OCR 引擎实识别(飞桨 @gutenye/ocr-node)
- **说明**:本次仅检测未修改任何代码,问题留给同事修复。
- **修复状态(2026-11-02 更新)**:报告中的全部问题已由刘爱家修复完成——
- 提交 `342145c`:P2-2(屏幕截图图像来源/截图范围,含区域裁剪与下拉选项)
- 提交 `ba17d6d`:P0-1(GetOcrTextNew 传图键)、P1-1(点击位置九宫格/偏移)、P1-2(鼠标按键左/右/中/双击)、P1-3(GetOcrTable 行列聚类 + 写数据表 + 存 Excel)、P2-1(查找范围指定窗口/指定区域)、P3-2(鼠标移动轨迹动画)、P3-3(截图空路径落证据目录;表格行内按 x 排序);P3-1(accuracy2)无特征匹配层实现,已在目录中隐藏并注明原因
- 回归:typecheck 通过;vitest 256 过(4 失败为与本次无关的桌面/PDF 环境遗留项);新增 vision-helpers 单测 12 项覆盖区域解析/裁剪/九宫格
---
## 一、结论概览
| 组件 | 名称 | 可用性 | 核心问题 |
|---|---|---|---|
| Image_Screenshot | 屏幕截图 | ⚠️ 基本可用 | 截图范围/图像来源字段无效,仅支持全屏 |
| Image_Find | 查找图像 | ✅ 可用 | accuracy2 字段无效 |
| Image_Find_V1 | 查找图像 | ⚠️ 有隐患 | window_obj 必填但被忽略,实际全屏查找 |
| Image_Click | 点击图像 | ⚠️ 有缺陷 | position(点击位置)被忽略,永远点中心 |
| Image_Click_V1 | 点击图像 | ⚠️ 有缺陷 | 同上 |
| Image_Click_V2 | 点击图像 | ⚠️ 有缺陷 | 同上 |
| Image_Click_V3 | 鼠标点击图像 | ❌ 功能缺失 | click_key(左/右键)被忽略,右键点击无法实现;窗口限定无效 |
| OcrOffline_GetOcrTextNew | 图像OCR识别 | ❌ 不可用 | **按目录配置无法传入图片路径,任何填写方式都报「图片路径为空」** |
| OcrOffline_GetOcrTextPosition | 图像OCR识别(含坐标) | ✅ 可用 | 无阻塞性问题 |
| OcrOffline_GetOcrTable | 获取表格数据(OCR) | ⚠️ 部分可用 | 保存至数据表/Excel 的 4 个字段全部无效;返回扁平文本非表格结构 |
**正常项(已实测确认)**:截屏产物正确;模板匹配精度高(自测得分 0.9993);OCR 中文/数字识别准确、坐标与置信度返回正常;找图失败时报错提示合理。
---
## 二、问题清单(按严重度)
### P0-1 OcrOffline_GetOcrTextNew:目录字段与实现完全脱节,组件不可用
- **现象**:该组件在目录里唯一的图片相关字段是 `uiSelector`(目标,非必填),而实现读取图片路径的键列表为 `image_path/imagePath/file_path/filePath/path/image/pictureSource`——**不包含 uiSelector**。
- **后果**:用户按目录界面填参数,无论怎么填都无法把图片传进实现,必然报「图片路径为空」。已实测复现:`{ uiSelector: "D:/.../ocr_input.png" }` → `RuntimeError: 图片路径为空`。
- **修复建议**:实现读取键中加入 `uiSelector`,或目录为该组件增加 `image_path` 必填字段(与 GetOcrTextPosition 保持一致)。
### P1-1 Image_Click 全 4 版本:忽略「点击位置」字段
- **现象**:目录字段 `position`(点击位置,默认 center),实现拿到匹配区域后**固定点击中心点**(`vision.ts` 中 `x + width/2, y + height/2`),position 值从不读取。
- **后果**:用户选择「左上角/右上角/自定义偏移」等位置时静默失效,点击落点与预期不符且无任何提示。
### P1-2 Image_Click_V3:忽略「鼠标按键」字段,右键点击无法实现
- **现象**:目录字段 `click_key`(鼠标按键,默认 left),实现写死左键单击(`mouse_event(2,...)`按下 + `mouse_event(4,...)`弹起)。
- **后果**:右键菜单类自动化场景完全无法实现,且不报错(静默按左键执行)。
### P1-3 OcrOffline_GetOcrTable:输出相关 4 个字段全部无效
- **现象**:目录字段 `showTable`(保存至数据表)、`isExcel`(保存至Excel)、`excelName`、`excelPath`,实现全部不读取;仅返回按 y 坐标排序的**扁平文本行数组**。
- **后果**:「将结果保存至数据表/Excel」的承诺静默不执行;且行内多列文本不拆分(如「鼠标 20」作为一整行文本返回),拿不到结构化表格。
### P2-1 Image_Find_V1 / Image_Click_V3:「窗口对象」必填但被忽略
- **现象**:目录要求 `window_obj` 必填、`find_range`(图像查找范围)可选,实现两者都不读取,永远全屏截图查找。
- **后果**:用户以为限定了窗口范围,实际全屏找图——找到错误目标(其他窗口的同款图标)或找不到时无从排查。且 window_obj 必填迫使用户必须配置一个无意义的参数。
### P2-2 Image_Screenshot:「截图范围」「图像来源」字段无效
- **现象**:目录字段 `pictureSource`(图像来源)、`shotRange`(截图范围),实现不读取,固定全屏(`PrimaryScreen`)。目录里还有 `action/position/position_img` 三个疑似遗留占位字段。
- **后果**:只支持全屏截图;另外多显示器环境只截主屏,副屏内容丢失。
### P3-1 「特征相似度」accuracy2 字段无效
- Image_Find / Image_Click / Image_Click_V1 的 `accuracy2`(特征相似度)字段,实现不读取。当前实现是纯像素差模板匹配,没有特征匹配层,该字段为冗余配置。
### P3-2 「展示鼠标移动轨迹」show_track 字段无效
- Image_Click 全版本的 `show_track` 被忽略。当前实现 SetCursorPos 直接跳转,无轨迹动画。低优先级。
### P3-3 其他小问题
- Image_Screenshot 当 `image_path` 为空时输出文件落盘到进程工作目录(不可预期);因该字段必填,实际风险低,建议加显式校验。
- OcrOffline_GetOcrTable 的行排序仅按 y 坐标,同一行内多个文本块不合并、不按 x 排序,多列场景顺序不稳定。
---
## 三、实测验证记录(正常部分证据)
| 测试 | 结果 |
|---|---|
| GDI 生成测试图(600×200,含「RPA流程测试ABC123」「名称 数量」「鼠标 20」) | 成功 |
| Image_Screenshot 截屏逻辑(同款 PowerShell 脚本) | 成功,产物 246KB PNG |
| findImage 模板匹配(app_logo_tpl 模板 vs 实时截屏) | 命中 (93,27),得分 **0.9993** |
| OcrOffline_GetOcrTextNew(传入 image_path 时) | 识别结果 `RPA流程测试ABC123\n名称 数量\n鼠标 20` 完全正确 |
| OcrOffline_GetOcrTextPosition | 3 行文本坐标 + 置信度(0.999/0.949/0.899)返回正常 |
| 找图失败路径 | 正确抛「屏幕中未找到目标图像」并附建议(注:vitest 环境内 PowerShell 受 PATH 限制报 ENOENT,真实应用主进程不受影响) |
**说明**:OCR 引擎(飞桨)与模板匹配算法本身质量良好,问题集中在**目录字段与实现的键名/功能脱节**——与此前 Office、数据处理组发现的拼接列、单元格格式问题是同一类「接入只对了一半」的缺陷。
---
## 四、给同事的修复优先级建议
1. **先修 P0-1**(GetOcrTextNew 传图键)——1 行改动,组件从不可用变可用
2. **再修 P1 三项**(Click 的 position/click_key、GetOcrTable 的输出字段)——都是读键 + 少量分支逻辑
3. **P2/P3 可排后**——窗口限定、截图范围属于功能增强,当前行为可接受但建议在 UI 上隐藏无效字段避免误导

View File

@ -16,7 +16,7 @@
"typecheck": "tsc --noEmit -p tsconfig.json",
"check:deps": "node packages/rpa-core/scripts/check-deps.mjs",
"rpa-cli": "tsx scripts/rpa-cli.ts",
"catalog": "node scripts/build-catalog.mjs",
"catalog": "tsx scripts/build-catalog.mjs",
"snapshot": "vitest run scripts/capability-snapshot.test.ts",
"package": "node scripts/package.mjs",
"test": "vitest run",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -18,7 +18,7 @@ function run(id: string, args: Record<string, unknown> = {}, signal = new AbortC
afterEach(() => { vi.useRealTimers(); vi.resetAllMocks(); });
describe("应用与系统组件缺陷回归", () => {
describe.skipIf(process.platform !== "win32")("应用与系统组件缺陷回归", () => {
it("没有匹配进程时返回 [] / null,忽略空行和非法 PID", async () => {
mocks.powershell.mockResolvedValue("");
await expect(run("App_GetPids", { pattern: "missing.exe" })).resolves.toEqual([]);

View File

@ -33,7 +33,7 @@ async function run(componentId: string, args: Record<string, unknown>, ctxOverri
return handler(ctxOverride ?? ctx, n, args, {} as CatalogComponent);
}
describe("应用与系统组件冒烟", () => {
describe.skipIf(process.platform !== "win32")("应用与系统组件冒烟", () => {
it("数据类型判断族", async () => {
expect(await run("Basic_IsNumber", { val: "12.5" })).toBe(true);
expect(await run("Basic_IsNumber", { val: "abc" })).toBe(false);

View File

@ -14,7 +14,7 @@ async function run(id: string, args: Record<string, unknown>) {
return handler!(context, node(id), args, component);
}
describe("manual keyboard and encrypted variables", () => {
describe.skipIf(process.platform !== "win32")("manual keyboard and encrypted variables", () => {
it("registers manual keyboard switches", async () => {
await expect(run("Keyboard_EnableManualOperation", { random_move: true, random_click: true, random_sleep: false, min_time: 0, max_time: 0 })).resolves.toMatchObject({ enabled: true });
await expect(run("Keyboard_DisableManualOperation", {})).resolves.toEqual({ enabled: false });

View File

@ -1,4 +1,5 @@
import { existsSync } from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { getHandler } from "../engine/registry";
import type { CatalogComponent, FlowNode } from "../types";
@ -7,7 +8,10 @@ import "./remaining-core";
import "./captcha-caps"; // last-wins:覆盖 remaining-core 的通用实现
import { cleanCodeText, parseCalculation } from "./captcha-caps";
const ctx = { signal: new AbortController().signal, log: () => {}, vars: {}, evidenceDir: "D:/office-test2/ocr-test" } as unknown as RunContext;
// 测试图随仓库走(__fixtures__/captcha,PIL 生成),真实 OCR 模型来自 npm 依赖
const fixturesDir = path.join(import.meta.dirname, "__fixtures__", "captcha");
const fixture = (name: string) => path.join(fixturesDir, `${name}.png`);
const ctx = { signal: new AbortController().signal, log: () => {}, vars: {}, evidenceDir: fixturesDir } as unknown as RunContext;
const node: FlowNode = { id: "smoke", componentId: "smoke", position: { x: 0, y: 0 }, values: {}, options: {} };
describe("parseCalculation 解析层(纯逻辑)", () => {
@ -36,7 +40,7 @@ describe("parseCalculation 解析层(纯逻辑)", () => {
});
});
describe.skipIf(!existsSync("D:/office-test2/ocr-test/captcha_0_3p5_q.png"))("OCR_CalculationCode 端到端(真实模型)", () => {
describe.skipIf(!existsSync(fixture("captcha_0_3p5_q")))("OCR_CalculationCode 端到端(真实模型)", () => {
const cases: Array<[string, string]> = [
["captcha_0_3p5_q", "8"],
["captcha_1_12m7_q", "5"],
@ -48,7 +52,7 @@ describe.skipIf(!existsSync("D:/office-test2/ocr-test/captcha_0_3p5_q.png"))("OC
it(`${file} -> ${expected}`, async () => {
const handler = getHandler("OCR_CalculationCode");
expect(handler).toBeTruthy();
const out = await handler!(ctx, node, { path: `D:/office-test2/ocr-test/${file}.png` }, {} as CatalogComponent);
const out = await handler!(ctx, node, { path: fixture(file) }, {} as CatalogComponent);
console.log(`[${file}]`, String(out));
expect(String(out)).toBe(expected);
}, 300000);

View File

@ -20,7 +20,7 @@ async function invoke(componentId: string, args: Record<string, unknown>): Promi
return handler!(ctx, node(componentId), args, component);
}
describe("键盘输入组件真实冒烟", () => {
describe.skipIf(process.platform !== "win32")("键盘输入组件真实冒烟", () => {
it("words 字段取值(此前读不到报「键盘输入为空」), 后台接口模式整段发送特殊字符不抛错", async () => {
const out = await invoke("Keyboard_Send_V1", { words: "哈哈哈(测试)100%^2", interval: "10", fuctype: "0" });
expect(out).toBe("哈哈哈(测试)100%^2");

View File

@ -52,7 +52,7 @@ afterEach(async () => {
if (original) await invoke("Mouse_MoveToV2", { location: { x: original[0], y: original[1] }, position: "0", move_type: "2", speed: 100 });
});
describe("移动鼠标组件真实冒烟", () => {
describe.skipIf(process.platform !== "win32")("移动鼠标组件真实冒烟", () => {
it("对象坐标 x/y JSON 不再报无效, 瞬间移动真实落位", async () => {
original = await cursorPos();
// 用户屏幕框选生成的浮点对象坐标(此前直接报「鼠标坐标无效」)

View File

@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { capabilityCount, getCapabilityMeta } from "../engine/registry";
import {
PLATFORM_GROUPS,
buildDomainPlatformMap,
capabilityDomains
} from "./register-all";
describe("PLATFORM_GROUPS 平台单一源(迁移指南五件小事 #1)", () => {
it("每个能力域都有平台行,不允许缺行", () => {
for (const domain of capabilityDomains) {
expect(PLATFORM_GROUPS[domain], `域 ${domain} 缺少 PLATFORM_GROUPS 行`).toBeDefined();
}
expect(Object.keys(PLATFORM_GROUPS).sort()).toEqual([...capabilityDomains].sort());
});
it("现阶段数据只允许 win32,darwin/linux 仅类型占位不填数据", () => {
for (const [domain, platforms] of Object.entries(PLATFORM_GROUPS)) {
expect(
platforms.every((p) => p === "win32"),
`域 ${domain} 填了 win32 以外的平台数据`
).toBe(true);
}
});
it("registerAll 后所有能力 meta.platforms 与分组表一致(运行时派生)", async () => {
// buildDomainPlatformMap 逐域加载(动态 import 有缓存,必须在 registerAll 语义内一次完成)
const map = await buildDomainPlatformMap();
expect(capabilityCount()).toBeGreaterThan(700);
expect(map.size).toBe(capabilityCount());
for (const id of map.keys()) {
const meta = getCapabilityMeta(id);
expect(meta, `能力 ${id} 无 meta`).toBeTruthy();
expect(meta!.platforms, `能力 ${id} 的 meta.platforms 未从分组表派生`).toEqual(["win32"]);
}
});
});

View File

@ -1,4 +1,53 @@
import type { RpaEngine } from "../engine/engine";
import { applyPlatformDefaults, capabilityIds, type CapabilityPlatform } from "../engine/registry";
export type { CapabilityPlatform } from "../engine/registry";
/**
* 平台归属单一权威表(迁移指南「五件小事 #1」,dev-conventions 规则 4)。
*
* - 运行时 `meta.platforms` 与构建期 catalog `platformTag` 都从这张表派生,
* 任何代码不得自行声明第二份平台数据(守卫测试 platform-groups.test.ts 把关)。
* - 现阶段所有能力只在 Windows 交互会话验证过,全部标 `["win32"]`;
* `darwin`/`linux` 仅类型占位,不填数据。将来支持新平台 = 对应行加平台,仅此而已。
* - 后续若某域内出现平台混装(如 remaining-core 里混着 PS 专用能力),
* 先把域拆细,再在能力 meta.platforms 里显式声明更窄限制。
*/
export const PLATFORM_GROUPS: Record<string, CapabilityPlatform[]> = {
control: ["win32"],
files: ["win32"],
http: ["win32"],
data: ["win32"],
office: ["win32"],
desktop: ["win32"],
desktopUia: ["win32"],
time: ["win32"],
windowsSystem: ["win32"],
ftp: ["win32"],
ocr: ["win32"],
pdf: ["win32"],
word: ["win32"],
wordExtended: ["win32"],
message: ["win32"],
vision: ["win32"],
cvVision: ["win32"],
extended: ["win32"],
systemExtended: ["win32"],
officeFileExtended: ["win32"],
officeAliases: ["win32"],
excelCommon: ["win32"],
excelCom: ["win32"],
dataExtended: ["win32"],
utilityExtended: ["win32"],
dataTableCommon: ["win32"],
basicEncryption: ["win32"],
mailRemaining: ["win32"],
remainingCore: ["win32"],
commonFinal: ["win32"],
humanDialog: ["win32"],
aiService: ["win32"],
windowExtended: ["win32"]
};
/**
* 显式注册入口(任务 4.2,决策点 9:双入口过渡)。
@ -46,14 +95,35 @@ const DOMAINS: Record<string, () => Promise<unknown>> = {
windowExtended: () => import("./window-extended")
};
async function loadDomainWithPlatformDefaults(domain: string): Promise<Set<string>> {
const load = DOMAINS[domain];
if (!load) throw new Error(`未知能力域:${domain}(可选:${Object.keys(DOMAINS).join(", ")})`);
const before = new Set(capabilityIds());
await load();
const fresh = [...capabilityIds()].filter((id) => !before.has(id));
// 平台默认值从 PLATFORM_GROUPS 派生(单一源);能力显式声明的 platforms 优先
applyPlatformDefaults(fresh, PLATFORM_GROUPS[domain] ?? []);
return new Set(fresh);
}
export async function registerAll(engine: RpaEngine, groups?: string[]): Promise<RpaEngine> {
const wanted = groups && groups.length ? groups : Object.keys(DOMAINS);
for (const group of wanted) {
const load = DOMAINS[group];
if (!load) throw new Error(`未知能力域:${group}(可选:${Object.keys(DOMAINS).join(", ")})`);
await load();
}
for (const group of wanted) await loadDomainWithPlatformDefaults(group);
return engine;
}
/**
* 逐域加载并返回「能力 id → 平台数组」映射(副作用:完成全量注册)。
* 供构建脚本(build-catalog)派生 catalog platformTag,与运行时同一张表。
*/
export async function buildDomainPlatformMap(): Promise<Map<string, CapabilityPlatform[]>> {
const map = new Map<string, CapabilityPlatform[]>();
for (const domain of Object.keys(DOMAINS)) {
const fresh = await loadDomainWithPlatformDefaults(domain);
const platforms = PLATFORM_GROUPS[domain] ?? [];
for (const id of fresh) map.set(id, platforms);
}
return map;
}
export const capabilityDomains = Object.keys(DOMAINS);

View File

@ -4,6 +4,7 @@ import path from "node:path";
import { promisify } from "node:util";
import { register } from "../engine/registry";
import { RuntimeError } from "../engine/types";
import { isWindows } from "../driver/platform";
type Table = unknown[][];
@ -257,7 +258,7 @@ register("System_ExecuteCommandV1", async (_ctx, node, args) => {
const timeout = Math.max(1_000, Number(args.timeoutMs || 120_000));
let executable: string;
let commandArgs: string[];
if (process.platform === "win32") {
if (isWindows) {
if (mode === "2") {
const file = path.resolve(requiredText(args.bat_file, "BAT文件", node.id, node.componentId));
await fs.access(file);
@ -280,7 +281,7 @@ register("System_ExecuteCommandV1", async (_ctx, node, args) => {
commandArgs = ["-lc", command];
}
// cmd.exe 自己解析 /c 后的命令;不能让 Node 再把 BAT 路径引号转成反斜杠引号。
const result = await execFileAsync(executable, commandArgs, { cwd, timeout, windowsHide: true, windowsVerbatimArguments: process.platform === "win32" && mode !== "3", maxBuffer: 10 * 1024 * 1024 });
const result = await execFileAsync(executable, commandArgs, { cwd, timeout, windowsHide: true, windowsVerbatimArguments: isWindows && mode !== "3", maxBuffer: 10 * 1024 * 1024 });
return bool(args.get_ret, true) ? String(result.stdout).trimEnd() : true;
});

View File

@ -0,0 +1,9 @@
/**
* 平台判断唯一入口(dev-conventions 规则 4 的例外白名单)。
*
* 规范:平台差异只允许出现在两处——`driver/<平台>/` 目录下的驱动实现,以及本文件;
* 能力文件不得直接调用 `process.platform`,需要平台分支时从这里取值。
*/
export const isWindows = process.platform === "win32";
export const isMacOS = process.platform === "darwin";
export const isLinux = process.platform === "linux";

View File

@ -0,0 +1,22 @@
import { describe, expect, it } from "vitest";
import { createEngine } from "./engine";
import { WindowsDriver } from "../driver/windows/windows-driver";
describe("driver 平台自动选择(迁移指南五件小事 #2)", () => {
it("win32 上惰性创建 WindowsDriver 且实例缓存", () => {
const engine = createEngine();
expect(engine.driver).toBeInstanceOf(WindowsDriver);
expect(engine.driver).toBe(engine.driver);
});
it("非 win32 访问 driver 抛 platform_unsupported", () => {
const engine = createEngine();
const original = process.platform;
Object.defineProperty(process, "platform", { value: "darwin", configurable: true });
try {
expect(() => engine.driver).toThrowError(/暂不支持桌面自动化驱动/);
} finally {
Object.defineProperty(process, "platform", { value: original, configurable: true });
}
});
});

View File

@ -2,6 +2,7 @@ import type { Client } from "basic-ftp";
import type { Workbook as ExcelJSWorkbook } from "exceljs";
import { WindowsDriver } from "../driver/windows/windows-driver";
import type { AutomationDriver } from "../driver/types";
import { RuntimeError } from "./types";
/** 人为拟真操作的开关与随机参数(desktop 能力的进程级设置)。 */
export interface ManualOperationSettings {
@ -41,8 +42,23 @@ export class RpaEngine {
minMs: 1000,
maxMs: 3000
};
/** 桌面自动化驱动(任务 3.4 注入点,默认 PowerShell 版 WindowsDriver) */
readonly driver: AutomationDriver = new WindowsDriver();
/** 桌面自动化驱动:按系统自动选(迁移指南五件小事 #2)。win32 用 PowerShell 版 WindowsDriver;
* 其他平台尚未实现,访问时抛 platform_unsupported 明确报错(惰性创建,避免 import 即炸) */
private _driver?: AutomationDriver;
get driver(): AutomationDriver {
if (!this._driver) {
if (process.platform === "win32") {
this._driver = new WindowsDriver();
} else {
throw new RuntimeError(
"platform_unsupported",
`该平台暂不支持桌面自动化驱动(当前:${process.platform})`,
{ recommendation: "桌面/键鼠/窗口能力目前仅支持 Windows;macOS/Linux 驱动在迁移指南三端计划中规划" }
);
}
}
return this._driver;
}
}
export function createEngine(): RpaEngine {

View File

@ -40,6 +40,7 @@ export const RUNTIME_ERROR_CODES = [
"word_text_not_found",
"outlook_unavailable",
// 其他
"platform_unsupported",
"decryption_failed",
"host_unavailable"
] as const;

View File

@ -13,6 +13,9 @@ export interface CapabilityParamMeta {
description?: string;
}
/** 平台类型;现阶段只有 win32 有实现,darwin/linux 仅类型占位(dev-conventions 规则 4) */
export type CapabilityPlatform = "win32" | "darwin" | "linux";
export interface CapabilityMeta {
/** 一句话能力摘要(中文) */
summary?: string;
@ -27,7 +30,7 @@ export interface CapabilityMeta {
/** 执行中是否需要回传截图(Agent 侧视觉判断用) */
needsScreenshot?: boolean;
/** 平台限制;省略表示跨平台 */
platforms?: Array<"win32">;
platforms?: CapabilityPlatform[];
}
const handlers = new Map<string, CapabilityHandler>();
@ -60,3 +63,21 @@ export function capabilityIds(): string[] {
export function capabilityCount(): number {
return handlers.size;
}
/**
* 给一批能力落平台默认值(来自 register-all.ts 的 PLATFORM_GROUPS 单一权威表)。
* - 空数组 = 平台无限制,不落 meta;
* - 已有 meta 且已声明 platforms 的不覆盖(能力可显式声明更窄的平台限制);
* - 无 meta 的补一个只含 platforms 的 meta。
*/
export function applyPlatformDefaults(ids: string[], platforms: CapabilityPlatform[]): void {
if (!platforms.length) return;
for (const id of ids) {
const meta = metas.get(id);
if (meta) {
if (!meta.platforms) metas.set(id, { ...meta, platforms: [...platforms] });
} else {
metas.set(id, { platforms: [...platforms] });
}
}
}

View File

@ -1,5 +1,5 @@
{
"generatedAt": "2026-11-04T09:12:00.449Z",
"generatedAt": "2026-11-05T09:52:08.862Z",
"count": 909,
"ids": [
"App_GetPid",

View File

@ -176,7 +176,25 @@
"cvSelector": {
"description": "屏幕上可见的目标文本,运行时截屏后通过 OCR 定位(填按钮或界面上的文字,如「登录」)"
}
}
},
"removeFields": [
"searchRegion"
],
"addFields": [
{
"key": "searchRegion",
"label": "搜索区域",
"widget": "region",
"type": "object",
"displayType": "region",
"required": false,
"defaultValue": "",
"placeholder": "",
"description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找",
"advanced": false,
"valueTypes": []
}
]
},
"CV_SetText": {
"fields": {
@ -187,7 +205,25 @@
"description": "填写参数值,支持 ${变量} 引用上游输出",
"placeholder": "输入内容或 ${变量}"
}
}
},
"removeFields": [
"searchRegion"
],
"addFields": [
{
"key": "searchRegion",
"label": "搜索区域",
"widget": "region",
"type": "object",
"displayType": "region",
"required": false,
"defaultValue": "",
"placeholder": "",
"description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找",
"advanced": false,
"valueTypes": []
}
]
},
"CV_MouseHover": {
"fields": {
@ -200,7 +236,25 @@
"offsetY": {
"description": "相对目标中心的垂直偏移像素,正数向下、负数向上,0 为正中"
}
}
},
"removeFields": [
"searchRegion"
],
"addFields": [
{
"key": "searchRegion",
"label": "搜索区域",
"widget": "region",
"type": "object",
"displayType": "region",
"required": false,
"defaultValue": "",
"placeholder": "",
"description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找",
"advanced": false,
"valueTypes": []
}
]
},
"CV_MouseClick": {
"fields": {
@ -216,7 +270,25 @@
"offsetY": {
"description": "相对目标中心的垂直偏移像素,正数向下、负数向上,0 为正中"
}
}
},
"removeFields": [
"searchRegion"
],
"addFields": [
{
"key": "searchRegion",
"label": "搜索区域",
"widget": "region",
"type": "object",
"displayType": "region",
"required": false,
"defaultValue": "",
"placeholder": "",
"description": "可选。点「屏幕框选」框住目标所在区域,运行时只在该区域内查找目标文本,屏幕上有相同文字时可避免点错;留空则全屏查找",
"advanced": false,
"valueTypes": []
}
]
},
"Image_Click": {
"fields": {
@ -225,6 +297,9 @@
},
"show_track": {
"description": "勾选后运行时显示鼠标移动轨迹动画,便于观察执行过程"
},
"accuracy2": {
"required": false
}
}
},
@ -298,15 +373,14 @@
"Image_Screenshot": {
"fields": {
"image_path": {
"placeholder": "如 D:\\files\\demo.png"
"label": "截图保存路径"
},
"action": {
"description": "区域坐标 JSON;点右侧「屏幕框选」在屏幕上框选生成",
"placeholder": "{\"x\":0,\"y\":0,\"width\":100,\"height\":50}"
},
"position": {
"description": "区域坐标 JSON;点右侧「屏幕框选」在屏幕上框选生成",
"placeholder": "{\"x\":0,\"y\":0,\"width\":100,\"height\":50}"
"label": "截图区域"
},
"position_img": {
"description": "区域坐标 JSON;点右侧「屏幕框选」在屏幕上框选生成",
@ -332,7 +406,68 @@
"path": {
"description": "编码方向的源图片路径,支持 ${变量} 引用上游输出(如上游截图的保存路径)"
}
}
},
"removeFields": [
"mode",
"output",
"val"
],
"addFields": [
{
"key": "mode",
"label": "转换方向",
"widget": "radio",
"type": "string",
"displayType": "buttonGroup",
"required": false,
"defaultValue": "encode",
"placeholder": "",
"description": "编码:图片文件转为 base64 字符串;解码:base64 字符串还原保存为图片文件",
"advanced": false,
"valueTypes": [],
"options": [
{
"label": "图片 → Base64",
"value": "encode"
},
{
"label": "Base64 → 图片",
"value": "decode"
}
]
},
{
"key": "output",
"label": "图片保存路径",
"widget": "file",
"type": "object",
"displayType": "selectFile",
"required": false,
"defaultValue": "",
"placeholder": "如 D:\\files\\restored.png",
"description": "解码方向的保存位置(含文件名);留空自动生成临时图片文件",
"advanced": false,
"valueTypes": [
"rpa_str_file",
"rpa_str"
]
},
{
"key": "val",
"label": "Base64编码",
"widget": "text",
"type": "string",
"displayType": "text-expression",
"required": true,
"defaultValue": "",
"placeholder": "如 ${图片编码值}",
"description": "解码方向的 base64 字符串,支持 ${变量} 引用上游输出;自动忽略 data:image/...;base64, 前缀",
"advanced": false,
"valueTypes": [
"rpa_str"
]
}
]
},
"Image_Wait_V1": {
"fields": {
@ -413,7 +548,7 @@
"Image_Find_V1": {
"fields": {
"window_obj": {
"description": "目标窗口标题,留空则作用于当前活动窗口;点「选窗口」从可见窗口列表中选择"
"required": false
},
"action": {
"description": "区域坐标 JSON;点右侧「屏幕框选」在屏幕上框选生成",
@ -425,23 +560,75 @@
},
"image": {
"description": "模板图片文件的完整路径,用于匹配识别;点右侧「屏幕截取」可框选屏幕上的目标图案"
},
"accuracy": {
"label": "模板相似度"
}
}
},
"Image_Exists_V1": {
"fields": {
"image": {
"description": "模板图片文件的完整路径,用于匹配识别;点右侧「屏幕截取」可框选屏幕上的目标图案"
"required": true,
"label": "目标图像"
},
"accuracy": {
"description": "相似度阈值(0~1,越大越严格):0.9 表示屏幕上至少 90% 像素与模板一致才算找到。建议 0.8~0.9。"
"required": true,
"label": "模板相似度"
}
}
},
"removeFields": [
"find_range",
"timeout",
"window_obj"
],
"addFields": [
{
"key": "find_range",
"label": "图像查找范围",
"widget": "select",
"displayType": "dropDownSelect",
"defaultValue": "1",
"required": false,
"options": [
{
"label": "整个屏幕",
"value": "1"
},
{
"label": "指定窗口",
"value": "2"
}
],
"description": "在整个虚拟桌面(含副屏)中查找,或仅在指定窗口范围内查找",
"type": "string"
},
{
"key": "timeout",
"label": "等待超时(秒)",
"widget": "number",
"displayType": "text-expression",
"defaultValue": "0",
"required": false,
"description": "大于 0 时在该时间内每隔 0.3 秒重试查找,超时仍未找到才返回 false",
"type": "string"
},
{
"key": "window_obj",
"label": "窗口对象",
"widget": "text",
"displayType": "text-expression",
"defaultValue": "",
"required": false,
"description": "目标窗口标题,留空则作用于当前活动窗口;点「选窗口」从可见窗口列表中选择",
"type": "string"
}
]
},
"Image_CropImg": {
"fields": {
"file_path": {
"description": "要被裁剪的源图片完整路径——本组件从已有图片文件中裁出区域,不是截屏;截屏请用「截图图像」组件"
"label": "源图片地址"
},
"imagePath": {
"description": "文件夹的完整路径;点右侧按钮浏览选择,支持 ${变量} 引用"
@ -467,13 +654,16 @@
},
"show_track": {
"description": "勾选后运行时显示鼠标移动轨迹动画,便于观察执行过程"
},
"accuracy2": {
"required": false
}
}
},
"Image_Click_V3": {
"fields": {
"window_obj": {
"description": "目标窗口标题,留空则作用于当前活动窗口;点「选窗口」从可见窗口列表中选择"
"required": false
},
"action": {
"description": "区域坐标 JSON;点右侧「屏幕框选」在屏幕上框选生成",
@ -500,10 +690,10 @@
"Image_RelativePosition": {
"fields": {
"source": {
"description": "被搜索的较大图片路径(如整屏截图),支持 ${变量} 引用上游输出"
"label": "被搜索图像"
},
"template": {
"description": "要寻找的小图案完整路径(按钮/图标/文字块),可点右侧按钮选择文件"
"label": "查找目标(小图)"
}
}
},
@ -1346,5 +1536,626 @@
"description": "选择解压使用的执行方式;不确定时保持默认。"
}
}
},
"WebBrowser_RunJsInIeBySelector_v3": {
"fields": {
"waitResultTimeout": {
"required": false
}
}
},
"StrUtil_CreateRandomStr": {
"fields": {
"special_str": {
"required": false
},
"min_max_length": {
"required": false
},
"str_contain": {
"required": false
}
}
},
"Element_SetAttribute_v2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Element_FileUploader": {
"fields": {
"upload_time": {
"required": false
},
"wait_dialog_time": {
"required": false
},
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GetCookieNew": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_Wait": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_GetWeblistening": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_SetCookies": {
"fields": {
"web": {
"required": false
}
}
},
"Element_GetAttribute_v2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Trigger.Manual": {
"fields": {
"entryName": {
"required": false
}
}
},
"Keyboard_Send_V1": {
"removeFields": [
"window_title"
],
"addFields": [
{
"key": "window_title",
"label": "输入目标窗口",
"widget": "text",
"type": "string",
"displayType": "text-expression",
"required": false,
"defaultValue": "",
"placeholder": "留空则输入到当前焦点窗口",
"description": "选择目标窗口后,运行时会先把该窗口带到前台再输入内容",
"advanced": false,
"valueTypes": []
}
]
},
"Mail_ReplyMail_v2": {
"fields": {
"emails": {
"required": false
}
}
},
"WebBrowser_GoForward": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_Stop": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_GetScrollPos_V1": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_UploadDialog": {
"fields": {
"upload_time": {
"required": false
},
"web": {
"required": false
}
}
},
"Element_GetPieChartData": {
"fields": {
"v_offset": {
"required": false
},
"radius_ratio": {
"required": false
},
"designated_obj": {
"required": false
},
"h_offset": {
"required": false
}
}
},
"Mail_OperateMail": {
"fields": {
"emails": {
"required": false
}
}
},
"Element_GetButtonEnable_v3": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"StrUtil_SplitStr": {
"fields": {
"split_num": {
"required": false
}
}
},
"Element_SetValue_V6": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GoUrl": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_SetScrollPos_V1": {
"fields": {
"designated_selector": {
"required": false
},
"designated_position": {
"required": false
},
"web": {
"required": false
}
}
},
"logic_control_stop_return": {
"fields": {
"return_value": {
"required": false
}
}
},
"WebBrowser_GetScrollPos": {
"fields": {
"web": {
"required": false
}
}
},
"Element_MouseTo_V4": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Element_GetButtonEnable_v4": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GetBrowserPicV1": {
"fields": {
"pic_name": {
"required": false
},
"web": {
"required": false
}
}
},
"Trigger.FileArrival": {
"fields": {
"stableSeconds": {
"required": false
}
}
},
"WebBrowser_GetCookies": {
"fields": {
"web": {
"required": false
}
}
},
"Bivartable_HandleDfMissValue": {
"fields": {
"row_num_list": {
"required": false
},
"col_num_list": {
"required": false
},
"row_name_list": {
"required": false
},
"col_name_list": {
"required": false
}
}
},
"WebBrowser_StopWeblistening": {
"fields": {
"web": {
"required": false
}
}
},
"Image_Find": {
"fields": {
"accuracy2": {
"required": false
}
}
},
"Element_exception_monitor_v2": {
"fields": {
"monitor_frequency": {
"required": false
},
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GetDocument": {
"fields": {
"web": {
"required": false
}
}
},
"Element_GetAttribute_v3": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_Switch": {
"fields": {
"web": {
"required": false
}
}
},
"Mail_DoReplyMail_v1": {
"fields": {
"emails": {
"required": false
}
}
},
"concurrent_child_process_v1": {
"fields": {
"fun_name": {
"required": false
},
"call_type": {
"required": false
},
"fun_version": {
"required": false
}
}
},
"Element_Click_V6": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_SetScrollPos": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_Reload": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_GetCookie": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_GetTitle": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_GetUrl": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_Maximize": {
"fields": {
"web": {
"required": false
}
}
},
"child_modules_v1": {
"fields": {
"fun_name": {
"required": false
},
"call_type": {
"required": false
},
"fun_version": {
"required": false
}
}
},
"Element_SelectOperation_v2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_Minimize": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_Close": {
"fields": {
"web": {
"required": false
}
}
},
"Element_GetLineBarChartData": {
"fields": {
"offset": {
"required": false
},
"designated_obj": {
"required": false
}
}
},
"WebBrowser_DownloadFile_V1": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Element_GetText_V5": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Trigger.Schedule": {
"fields": {
"timezone": {
"required": false
}
}
},
"StrUtil_ListToString": {
"fields": {
"connectors_num": {
"required": false
}
}
},
"WebBrowser_StartWeblistening": {
"fields": {
"web": {
"required": false
}
}
},
"WebBrowser_SetCookies_V1": {
"fields": {
"web": {
"required": false
}
}
},
"Keyboard_Send": {
"removeFields": [
"window_title"
],
"addFields": [
{
"key": "window_title",
"label": "输入目标窗口",
"widget": "text",
"type": "string",
"displayType": "text-expression",
"required": false,
"defaultValue": "",
"placeholder": "留空则输入到当前焦点窗口",
"description": "选择目标窗口后,运行时会先把该窗口带到前台再输入内容",
"advanced": false,
"valueTypes": []
}
]
},
"WebBrowser_CloseWeb": {
"fields": {
"web": {
"required": false
}
}
},
"Element_CheckBoxOperation_v3": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"MessageNotification_Feishu_Message": {
"fields": {
"mentioned_list": {
"required": false
}
}
},
"WebBrowser_RunJsInIeBySelectorGround": {
"fields": {
"web": {
"required": false
}
}
},
"Word_SaveAs": {
"fields": {
"fileAddr": {
"required": false
}
}
},
"Mouse_ClickCombine_v4": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_RunJsInIeBySelector_v2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Bivartable_DfWriteToFile": {
"fields": {
"suffix": {
"required": false
}
}
},
"Element_WaitElement_v5": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_Switch_V1": {
"fields": {
"web": {
"required": false
}
}
},
"Element_Region2_v3": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GoBack": {
"fields": {
"web": {
"required": false
}
}
},
"Element_Shot_V2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"Element_ScrollIntoView_v2": {
"fields": {
"designated_obj": {
"required": false
}
}
},
"WebBrowser_GetBrowserPic": {
"fields": {
"web": {
"required": false
},
"pic": {
"required": false
}
}
},
"Element_SelectOperation_v3": {
"fields": {
"designated_obj": {
"required": false
},
"select_value_list": {
"required": false
}
}
},
"ScreenControl_Screenlock": {
"name": "屏幕锁屏",
"removeFields": [
"username",
"pwd",
"width",
"height",
"scale_factor",
"port"
]
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,12 +0,0 @@
{
"do_normalize": false,
"do_rescale": true,
"do_resize": true,
"feature_extractor_type": "ImageFeatureExtractor",
"resample": 2,
"rescale_factor": 0.00392156862745098,
"size_divisor": 16,
"size": {
"longest_edge": 640
}
}

View File

@ -13,6 +13,8 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { applyCatalogOverrides } from "./catalog-overrides.mjs";
// 平台标记单一源:从包内 PLATFORM_GROUPS 逐域派生(npm run catalog 用 tsx 运行)
import { buildDomainPlatformMap, PLATFORM_GROUPS } from "../packages/rpa-core/src/capabilities/register-all";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
@ -172,7 +174,17 @@ function normalizeVendorField(field) {
}
function loadCatalogSource() {
const raw = fs.readFileSync(SRC_CATALOG, "utf8");
// 优先环境变量/对面仓库路径;对面重构删除该文件后,回退到 temp/catalog-source/ 的历史恢复版
// (自 Agent-Employee commit 7bc627c8 恢复;temp/ 不入库,换机器需重新恢复)
const candidates = [SRC_CATALOG, path.join(ROOT, "temp", "catalog-source", "shizai-catalog-data.js")];
const found = candidates.find((p) => fs.existsSync(p));
if (!found) {
throw new Error(
`找不到目录源数据 shizai-catalog-data.js(尝试:${candidates.join(" ; ")})。` +
`可从 Agent-Employee 历史 commit 7bc627c8 恢复到 temp/catalog-source/`
);
}
const raw = fs.readFileSync(found, "utf8");
const start = raw.indexOf("=");
if (start < 0) throw new Error("catalog source has no assignment");
let body = raw.slice(start + 1).trim();
@ -228,10 +240,10 @@ function normalizeVendorComponent(component, vendorMap) {
const fields = (component.inputFields || []).map((f) => enrichFromVendor(normalizeVendorField(f), vendor));
const output = outputOf(component, vendor);
const risk = ["web", "app", "desktop", "message", "mobile"].includes(group) ? "high" : "low";
// 任务 3.4(决策点 7):平台标记只填 catalog 数据,渲染层置灰延后。
// Windows 专用组:desktop(user32/键鼠)、app(COM/进程)、office(Excel/Word COM)、vision(PowerShell 截屏);
// data/file/message 纯 Node,web 走 CDP,均视为跨平台(null)。
const WIN32_GROUPS = new Set(["desktop", "app", "office", "vision"]);
// 任务 3.4(决策点 7)+ 五件小事 #1:platformTag 从包内 PLATFORM_GROUPS 单一源派生
// (能力 id 命中包内注册 → win32;未实现的能力与宿主原生节点 → null 跨平台占位)。
const platforms = domainPlatformMap.get(component.id) || component.platforms;
const platformTag = component.platformTag || (platforms?.includes("win32") ? "win32" : null);
return {
id: component.id,
name: component.name,
@ -246,7 +258,7 @@ function normalizeVendorComponent(component, vendorMap) {
functionCalls: (component.functions || []).map((f) => ({ function: f.function, args: f.arguments || [], assignTo: f.assignTo || "" })),
version: component.version || "",
tags: component.tags || [],
platformTag: component.platformTag || (WIN32_GROUPS.has(group) ? "win32" : null),
platformTag,
icon: group,
risk,
fields,
@ -319,7 +331,11 @@ function normalizeTrigger(trigger) {
};
}
function main() {
async function main() {
// 平台单一源:逐域加载包内能力,得到「能力 id → 平台数组」映射(与运行时同一张 PLATFORM_GROUPS 表)
domainPlatformMap = await buildDomainPlatformMap();
console.log(`platform map: ${domainPlatformMap.size} capability ids from ${Object.keys(PLATFORM_GROUPS).length} domains`);
const source = loadCatalogSource();
const vendorMap = loadComponentsNew();
@ -362,4 +378,10 @@ function main() {
console.log(`output: ${OUT_FILE} (${(size / 1024 / 1024).toFixed(2)} MB)`);
}
main();
/** 能力 id → 平台数组(buildDomainPlatformMap 的结果,main 内填充) */
let domainPlatformMap = new Map();
main().catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -7,10 +7,17 @@ export function applyCatalogOverrides(catalog) {
for (const component of catalog.components) {
const override = overrides[component.id];
if (!override) continue;
const { fields = {}, removeFields = [], ...metadata } = override;
const { fields = {}, addFields = [], removeFields = [], ...metadata } = override;
Object.assign(component, metadata);
component.fields = component.fields.filter(field => !removeFields.includes(field.key))
.map(field => ({ ...field, ...fields[field.key] }));
// addFields:补源数据里缺失、但实现确实读取的字段(完整字段定义,key 重复则跳过)
if (addFields.length) {
const have = new Set(component.fields.map(f => f.key));
for (const f of addFields) {
if (f && f.key && !have.has(f.key)) component.fields.push(f);
}
}
}
catalog.counts.fields = catalog.components.reduce((count, component) => count + component.fields.length, 0);
return catalog;

View File

@ -1,61 +0,0 @@
/**
* 通过 CDP 读取运行中的 Electron 页面:文本、节点数量与控制台错误。
* 用法:node scripts/cdp-probe.mjs [expression]
*/
const endpoint = process.env.CDP_ENDPOINT || "http://127.0.0.1:9223";
const targets = await (await fetch(`${endpoint}/json/list`)).json();
const page = targets.find((item) => item.type === "page");
if (!page) throw new Error("no page target");
const ws = new WebSocket(page.webSocketDebuggerUrl);
const pending = new Map();
const events = [];
let seq = 0;
function send(method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++seq;
pending.set(id, { resolve, reject });
ws.send(JSON.stringify({ id, method, params }));
});
}
await new Promise((resolve, reject) => {
ws.onopen = resolve;
ws.onerror = reject;
});
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.id && pending.has(message.id)) {
const { resolve, reject } = pending.get(message.id);
pending.delete(message.id);
if (message.error) reject(new Error(JSON.stringify(message.error)));
else resolve(message.result);
} else {
events.push(message);
}
};
await send("Runtime.enable");
await send("Log.enable");
const expression = process.argv[2] || "document.body.innerText.slice(0, 6000)";
const result = await send("Runtime.evaluate", {
expression,
returnByValue: true,
awaitPromise: true
});
console.log("=== result ===");
console.log(JSON.stringify(result.result?.value ?? result, null, 2));
const errors = events.filter(
(item) =>
item.method === "Runtime.exceptionThrown" ||
(item.method === "Log.entryAdded" && ["error", "warning"].includes(item.params?.entry?.level))
);
console.log("\n=== errors/warnings ===");
console.log(JSON.stringify(errors.map((item) => item.params), null, 2).slice(0, 6000));
ws.close();

View File

@ -1,160 +0,0 @@
import { spawn, spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const root = process.cwd();
const debugPort = 9233;
const profile = fs.mkdtempSync(path.join(os.tmpdir(), "rpa-studio-desktop-e2e-"));
const workDir = path.join(os.tmpdir(), "rpa-desktop-test");
const noteFile = path.join(workDir, "rpa-desktop-test.txt");
const titleMarker = "rpa-desktop-test";
const typedText = "RPA-Keyboard-Mouse-Test-OK";
const uiaText = "UIA-SetValue-写入验证";
const candidates = [process.env.RPA_ELECTRON_DIST, "C:/electron-rpa/dist", path.join(root, "node_modules", "electron", "dist")].filter(Boolean);
const dist = candidates.find((candidate) => fs.existsSync(path.join(candidate, "electron.exe")));
if (!dist) throw new Error("找不到 Electron 运行时,请设置 RPA_ELECTRON_DIST");
const env = { ...process.env, RPA_E2E: "1" };
delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(path.join(dist, "electron.exe"), [root, "--no-sandbox", `--remote-debugging-port=${debugPort}`, `--user-data-dir=${profile}`], {
cwd: root, env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true
});
let stderr = "";
child.stderr.on("data", (chunk) => { stderr += String(chunk); });
async function waitForTarget() {
const deadline = Date.now() + 30000;
while (Date.now() < deadline) {
if (child.exitCode !== null) throw new Error(`RPA Studio 提前退出:${child.exitCode}\n${stderr}`);
try {
const targets = await (await fetch(`http://127.0.0.1:${debugPort}/json/list`)).json();
const page = targets.find((item) => item.type === "page" && item.title === "RPA Studio");
if (page) return page;
} catch { /* wait for CDP */ }
await new Promise((resolve) => setTimeout(resolve, 200));
}
throw new Error(`等待 RPA Studio CDP 超时\n${stderr}`);
}
async function evaluate(page, expression) {
const socket = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((resolve, reject) => { socket.onopen = resolve; socket.onerror = reject; });
const result = await new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error("E2E Runtime.evaluate 超时")), 180000);
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.id !== 1) return;
clearTimeout(timer);
if (message.error) reject(new Error(JSON.stringify(message.error)));
else if (message.result?.exceptionDetails) reject(new Error(message.result.exceptionDetails.text || "E2E 页面异常"));
else resolve(message.result?.result?.value);
};
socket.send(JSON.stringify({ id: 1, method: "Runtime.evaluate", params: { expression, returnByValue: true, awaitPromise: true } }));
});
socket.close();
return result;
}
function node(componentId, values, label, outputVar, timeoutMs = 20000) {
return { componentId, values, label, position: { x: 220, y: 0 }, options: { timeoutMs, logException: true, ...(outputVar ? { outputVar } : {}) } };
}
const maximizeScript = "Add-Type -MemberDefinition '[DllImport(\"user32.dll\")] public static extern bool ShowWindow(IntPtr h, int cmd);' -Name RpaMax -Namespace WinRpaMax | Out-Null; $p = Get-Process notepad | Where-Object { $_.MainWindowHandle -ne 0 } | Select-Object -First 1; if ($p) { [WinRpaMax.RpaMax]::ShowWindow($p.MainWindowHandle, 3) | Out-Null; 'maximized' } else { 'no-window' }";
const cursorPosScript = "Add-Type -AssemblyName System.Windows.Forms; $p=[System.Windows.Forms.Cursor]::Position; \"$($p.X),$($p.Y)\"";
const nodes = [
node("Trigger.Manual", { entryName: "开始桌面键鼠实测" }, "人工发起"),
node("System_ExecuteCommand", { action: `New-Item -ItemType Directory -Force -Path '${workDir}' | Out-Null; Set-Content -LiteralPath '${noteFile}' -Value '' -Encoding UTF8` }, "准备测试文件"),
node("App_Run", { executeable: "notepad.exe", params: noteFile }, "启动记事本", "notepadPid"),
node("Basic_Delay", { mileSecond: 1500 }, "等待窗口出现"),
node("System_ExecuteCommand", { action: maximizeScript }, "最大化记事本", "maximized", 15000),
node("Window_SetActive", { match_title: titleMarker }, "激活记事本", "activatedTitle"),
node("Window_GetActive", {}, "读取前台窗口", "activeTitle1"),
node("Window_WindowExists", { match_title: titleMarker }, "窗口存在", "exists1"),
node("Keyboard_Send", { keys: typedText }, "键盘输入英文", "typed"),
node("Keyboard_Send", { keys: "^a^c" }, "全选并复制"),
node("Clipboard_GetText", {}, "读取剪贴板", "clipboard1"),
node("Mouse_MoveTo", { position: "400,300" }, "鼠标移动", "movedTo"),
node("System_ExecuteCommand", { action: cursorPosScript }, "读取真实光标位置", "cursorPos", 15000),
node("Mouse_RollTo", { RollNum: 1 }, "滚轮滚动", "rolled"),
node("Window_SetActive", { match_title: titleMarker }, "重新激活记事本", "activatedTitle2"),
node("Element_ExistsGround", { uiSelector: `window=${titleMarker};type=Edit;index=1` }, "UIA 元素存在", "uiaExists"),
node("Element_SetValueGround", { uiSelector: `window=${titleMarker};type=Edit;index=1`, value: uiaText }, "UIA 写入文本"),
node("Keyboard_Send", { keys: "^a^c" }, "再次全选复制"),
node("Clipboard_GetText", {}, "读取剪贴板二", "clipboard2"),
node("Element_GetValueGround", { uiSelector: `window=${titleMarker};type=Edit;index=1` }, "UIA 读取文本", "uiaValue"),
node("Element_ClickGround", { uiSelector: `window=${titleMarker};type=Edit;index=1` }, "UIA 点击编辑区", "clickResult"),
node("Window_SetActive", { match_title: titleMarker }, "清理前重新激活", "activatedTitle3"),
node("Keyboard_Send", { keys: "^a{DEL}" }, "清空文本避免保存提示"),
node("Window_CloseWindow", { title: titleMarker }, "关闭记事本", "closed"),
node("Basic_Delay", { mileSecond: 800 }, "等待退出"),
node("Window_WindowExists", { match_title: titleMarker }, "窗口已消失", "exists2")
];
const expression = `(async () => {
const flow = await window.rpa.flows.create("桌面键鼠实测");
const list = ${JSON.stringify(nodes)};
flow.nodes = list.map((item, index) => ({ id: "n" + String(index + 1).padStart(2, "0"), ...item, position: { x: 220, y: 80 + index * 140 } }));
flow.edges = flow.nodes.slice(1).map((item, index) => ({ id: "e" + index, source: flow.nodes[index].id, target: item.id }));
await window.rpa.flows.save(flow);
const events = [];
const off = window.rpa.run.onEvent((event) => events.push(event));
const started = await window.rpa.run.start(flow);
const finished = await new Promise((resolve, reject) => {
const deadline = Date.now() + 150000;
const timer = setInterval(() => {
const event = events.find((item) => item.type === "run.finished");
if (event) { clearInterval(timer); resolve(event); }
else if (Date.now() > deadline) { clearInterval(timer); reject(new Error("桌面键鼠流程超时")); }
}, 100);
});
off();
await window.rpa.flows.delete(flow.id);
const byNode = {};
for (const event of events) if (event.type === "node.finished") byNode[event.nodeId] = event;
return { runId: started.runId, status: finished.status, durationMs: finished.durationMs, byNode, logs: events.filter((e) => e.type === "log").map((e) => e.message) };
})()`;
let page;
try {
page = await waitForTarget();
const result = await evaluate(page, expression);
if (result?.status !== "succeeded") throw new Error(`桌面键鼠流程失败:${JSON.stringify(result, null, 2)}`);
const byNode = result.byNode || {};
const out = (id) => byNode[id]?.output;
const failedNodes = Object.entries(byNode).filter(([, event]) => event.status !== "succeeded");
const checks = [
["App_Run 返回 PID", Number(out("n03")) > 0, out("n03")],
["最大化记事本", out("n05") === "maximized", out("n05")],
["Window_SetActive 激活成功", String(out("n06") || "").includes(titleMarker), out("n06")],
["Window_GetActive 前台=记事本", String(out("n07") || "").includes(titleMarker), out("n07")],
["Window_WindowExists=true", out("n08") === true, out("n08")],
["Keyboard_Send 输入回显", out("n09") === typedText, out("n09")],
["剪贴板=键盘输入内容", out("n11") === typedText, out("n11")],
["Mouse_MoveTo 返回坐标", JSON.stringify(out("n12")) === JSON.stringify({ x: 400, y: 300 }), out("n12")],
["系统真实光标=目标位置", out("n13") === "400,300", out("n13")],
["Mouse_RollTo 滚动执行", out("n14") === 120, out("n14")],
["重复激活仍成功", String(out("n15") || "").includes(titleMarker), out("n15")],
["UIA 元素存在", out("n16") === true, out("n16")],
["UIA SetValue 后剪贴板一致", out("n19") === uiaText, out("n19")],
["UIA GetValue 读取一致", out("n20") === uiaText, out("n20")],
["UIA Click 执行", out("n21") && typeof out("n21") === "object", out("n21")],
["Window_CloseWindow 关闭", out("n24") === true, out("n24")],
["关闭后窗口不存在", out("n26") === false, out("n26")]
];
const problems = checks.filter(([, ok]) => !ok).map(([name, , actual]) => `${name} → ${JSON.stringify(actual)}`);
if (problems.length || failedNodes.length) {
throw new Error(`桌面键鼠实测未通过:\n${[...failedNodes.map(([id, event]) => `${id} ${event.status}: ${JSON.stringify(event.error || event.output)}`), ...problems].join("\n")}\n日志:\n${(result.logs || []).join("\n")}`);
}
console.log(JSON.stringify({ ok: true, runId: result.runId, durationMs: result.durationMs, checks: checks.map(([name]) => name) }, null, 2));
} finally {
if (process.platform === "win32") {
spawnSync("taskkill", ["/IM", "notepad.exe", "/F"], { windowsHide: true, stdio: "ignore" });
if (child.pid) spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
} else child.kill("SIGKILL");
await new Promise((resolve) => setTimeout(resolve, 500));
try { fs.rmSync(profile, { recursive: true, force: true, maxRetries: 12, retryDelay: 250 }); } catch {}
try { fs.rmSync(workDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 300 }); } catch {}
}

View File

@ -1,31 +0,0 @@
// 从运行记录的 tmp_table.json 重建比价表并导出 Excel
const ExcelJS = require("exceljs");
const fs = require("fs");
const table = JSON.parse(fs.readFileSync("C:/Users/Administrator/AppData/Roaming/rpa-studio/tmp_table.json", "utf8"));
// table[0] 是损坏的表头行(["标题"]),数据行从 index 1 开始:6 列 = 标题/价格/店铺/销量/空/AI
const dataRows = table.slice(1).filter((row) => row.length >= 4 && row[0]);
const aiText = table.map((row) => (row.length >= 6 ? row[5] : null)).find((c) => c && String(c).trim() && c !== null && String(c) !== "None") || "";
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("比价结果");
sheet.addRow(["标题", "价格", "店铺", "销量"]);
for (const row of dataRows) {
sheet.addRow([row[0], row[1], row[2], row[3]]);
}
// AI 分析单独放一个 sheet
const aiSheet = workbook.addWorksheet("AI分析");
aiSheet.addRow(["AI 比价分析结论"]);
aiSheet.getRow(1).font = { bold: true };
aiSheet.addRow([String(aiText)]);
aiSheet.getColumn(1).width = 120;
sheet.getColumn(1).width = 60;
sheet.getColumn(2).width = 12;
sheet.getColumn(3).width = 24;
sheet.getColumn(4).width = 14;
sheet.getRow(1).font = { bold: true };
const out = "C:/Users/Administrator/Desktop/淘宝手机比价.xlsx";
workbook.xlsx.writeFile(out).then(() => {
console.log("written:", out, "dataRows:", dataRows.length, "aiLen:", String(aiText).length);
});

View File

@ -1,40 +0,0 @@
import { spawn } from "node:child_process";
const executable = process.argv[2];
if (!executable) {
console.error("usage: node scripts/spawn-test.mjs <exe> [...args]");
process.exit(2);
}
const env = { ...process.env };
delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(executable, process.argv.slice(3), {
env,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: false
});
child.stdout.on("data", (data) => process.stdout.write(`[out] ${data}`));
child.stderr.on("data", (data) => process.stderr.write(`[err] ${data}`));
const timer = setTimeout(() => {
console.log(`STILL_RUNNING pid=${child.pid}`);
try {
child.kill("SIGKILL");
} catch {
// ignore
}
setTimeout(() => process.exit(0), 1000);
}, 9000);
child.on("exit", (code, signal) => {
clearTimeout(timer);
console.log(`EXIT code=${code} signal=${signal}`);
process.exit(0);
});
child.on("error", (error) => {
clearTimeout(timer);
console.error(`SPAWN_ERROR ${error.message}`);
process.exit(1);
});

View File

@ -41,11 +41,10 @@ export function localUiModelPath(): string {
app.getAppPath() ? path.join(app.getAppPath(), "resources") : "",
path.join(app.getAppPath(), "temp", "models")
].filter(Boolean);
// webpage-element 是实测后的首选模型;保留旧模型作为老安装包的兼容回退。
// webpage-element 是实测后的首选模型(旧 ui-icon-detect/model.onnx 兜底已移除,不再维护)
const fileCandidates = rootCandidates.flatMap(root => [
path.join(root, "models", "ui-element-detect", "webpage-element.onnx"),
path.join(root, "webpage-element.onnx"),
path.join(root, "models", "ui-icon-detect", "model.onnx")
path.join(root, "webpage-element.onnx")
]);
const found = fileCandidates.find(candidate => {
try { return fs.existsSync(candidate) && fs.statSync(candidate).isFile(); }