Files
PRA-only-demo/packages/rpa-core/src/engine/host-bridge.ts
T
yu 1a20dd72d1 feat: 视觉能力去 Electron 化,经 HostBridge.screen 注入(迁移阶段1-A9)
- HostBridge 新增可选 screen 服务(listDisplays/primaryDisplay/dipToScreenPoint/
  displayMatching/captureDisplay),vision 3 处、cv-vision 2 处动态 import(electron)
  全部改为 requireScreenService();未注入时抛稳定的 host_unavailable
- demo 宿主 src/main/index.ts 用 desktopCapturer/screen 实现 screen 服务
- check-deps.mjs 升级:新增动态 import()/require() 扫描(旧正则实测 0 命中的盲区),
  合成用例验证命中,升级后真实源码 0 违规
- 包依赖闭包:补声明 @cantoo/pdf-lib、@napi-rs/canvas、pdfjs-dist
- cv-vision 测试从 vi.mock(electron) 改为宿主桥注入,11/11 通过
2026-11-06 10:53:42 +08:00

111 lines
3.9 KiB
TypeScript
Raw 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.
import type { FlowDefinition } from "../types";
import { RuntimeError } from "./types";
/**
* 宿主桥(任务 2.1 正式化)。
*
* 包内能力需要的宿主能力(子流程仓库 / Electron 窗口 / DPI 换算 / 应用配置)
* 通过本接口注入,包本身禁止 import "electron" 或宿主模块(见包内依赖检查脚本)。
* 宿主启动时调用 setSubflowSource() / setHostBridge()。
*/
export interface SubflowSource {
listFlows(): Promise<Array<{ id: string; name: string }>>;
getFlow(id: string): Promise<FlowDefinition>;
}
let subflowSource: SubflowSource | undefined;
export function setSubflowSource(source: SubflowSource): void {
subflowSource = source;
}
export function getSubflowSource(): SubflowSource | undefined {
return subflowSource;
}
/** 人机交互表单窗口的最小面(能力只用到这些成员,宿主用 BrowserWindow 适配) */
export interface HostDialogWindow {
isDestroyed(): boolean;
destroy(): void;
loadURL(url: string): Promise<void>;
on(event: "closed", listener: () => void): void;
/** 在窗口页面里执行 JS 并取回结果(宿主侧以 userGesture=true 执行) */
executeJavaScript(script: string): Promise<unknown>;
}
export interface HostPoint {
x: number;
y: number;
}
/** AI 服务配置(宿主 settings.aiConfig) */
export interface HostAiConfig {
baseUrl: string;
apiKey: string;
model: string;
visionModel: string;
}
/** 显示器信息(视觉能力的屏幕枚举用;id 为宿主侧稳定标识字符串) */
export interface HostDisplay {
id: string;
bounds: { x: number; y: number; width: number; height: number };
scaleFactor: number;
}
/**
* 屏幕服务(迁移阶段1-A9):视觉能力原本经动态 import("electron") 拿
* desktopCapturer/screen,现改为宿主注入,Core 回归零 Electron 依赖。
*/
export interface HostScreenService {
/** 全部显示器 */
listDisplays(): HostDisplay[];
/** 主显示器 */
primaryDisplay(): HostDisplay;
/** DIP 屏幕坐标 → 物理像素 */
dipToScreenPoint(point: HostPoint): HostPoint;
/** 与该 DIP 矩形相交面积最大的显示器 */
displayMatching(rect: { x: number; y: number; width: number; height: number }): HostDisplay;
/**
* 抓取指定显示器当前画面,像素尺寸尽量接近 want 值,返回 PNG 字节。
* 无画面/抓取失败时抛错(调用方决定回退策略)。
*/
captureDisplay(displayId: string, wantWidth: number, wantHeight: number): Promise<Uint8Array>;
}
/** 宿主能力桥最小面(决策点 4):dialog 窗口工厂 / DPI 坐标换算 / 配置读取 / 屏幕服务 */
export interface HostBridge {
/** 创建人机交互表单窗口(human-dialog 用) */
createFormWindow(options: { title: string }): HostDialogWindow;
/** DIP 逻辑坐标 → 物理像素(window-extended 的鼠标/拖拽坐标换算) */
dipToScreenPoint(point: HostPoint): HostPoint;
/** 物理像素 → DIP 逻辑坐标 */
screenToDipPoint(point: HostPoint): HostPoint;
/** 读取 AI 服务配置(ai-service 用) */
loadAiConfig(): HostAiConfig;
/** 屏幕服务(vision/cv-vision 截屏与多屏几何);未注入时视觉能力报 host_unavailable */
screen?: HostScreenService;
}
let hostBridge: HostBridge | undefined;
export function setHostBridge(bridge: HostBridge): void {
hostBridge = bridge;
}
export function getHostBridge(): HostBridge | undefined {
return hostBridge;
}
/** 视觉能力取屏幕服务的统一入口:未注入宿主桥/屏幕服务时抛稳定错误码 */
export function requireScreenService(): HostScreenService {
const bridge = getHostBridge();
if (!bridge?.screen) {
throw new RuntimeError("host_unavailable", "宿主未注入屏幕服务(HostBridge.screen),截屏/视觉能力不可用", {
recommendation: "宿主启动时需调用 setHostBridge 并提供 screen 服务(参考 demo 的 src/main/index.ts)"
});
}
return bridge.screen;
}