- 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 通过
111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
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;
|
||
}
|