PRA-only-demo/docs/RPA-Review-Full.md

789 lines
60 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 实机测试。上述是源码级缺陷/风险定位,不等于已经在用户机器上复现。