阶段1-rpa-core建包: engine+31个纯Node能力迁入packages/rpa-core, 领域类型单一源化, createEngine工厂收拢模块级状态
- engine/(registry/types/runtime/expression)物理迁入包, 解耦shared/types(域类型归包, 宿主type-only re-export) - 31个纯Node能力+27个测试迁入包; 宿主留守Electron/浏览器/AI依赖能力, index.ts经@rpa/core/capabilities拉起 - 新增createEngine()/defaultEngine: excel缓存/Excel句柄/db连接/FTP客户端/OCR实例/拟真设置挂引擎实例 - remaining-core子流程依赖改为host-bridge注入, 包内不再import宿主flows - 宿主main/index.ts启动时注入子流程源; electron-vite/vitest/tsconfig配@rpa/core源码别名 - 验证: typecheck绿, 快照909个id diff为零, 332测试过(2个桌面冒烟因会话无交互SetForegroundWindow被拒, 环境阻塞)
This commit is contained in:
parent
4139400dbd
commit
caa8e19332
@ -6,9 +6,12 @@ export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@shared": resolve("src/shared")
|
||||
}
|
||||
alias: [
|
||||
{ find: "@shared", replacement: resolve("src/shared") },
|
||||
// rpa-core 包以源码形式打进主进程产物(不作为外部依赖运行时 require)
|
||||
{ find: /^@rpa\/core$/, replacement: resolve("packages/rpa-core/src/index.ts") },
|
||||
{ find: /^@rpa\/core\/capabilities$/, replacement: resolve("packages/rpa-core/src/capabilities/index.ts") }
|
||||
]
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
"private": true,
|
||||
"description": "Canvas-based RPA desktop application",
|
||||
"main": "./out/main/index.js",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "electron-vite dev",
|
||||
"dev:local": "node scripts/dev-safe.mjs",
|
||||
|
||||
40
packages/rpa-core/package.json
Normal file
40
packages/rpa-core/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@rpa/core",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "RPA 能力层:零 Electron 依赖的流程引擎、能力注册表与纯 Node 能力",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./capabilities": "./src/capabilities/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gutenye/ocr-node": "^1.4.9",
|
||||
"adm-zip": "^0.5.16",
|
||||
"basic-ftp": "^6.2.3",
|
||||
"exceljs": "^4.4.0",
|
||||
"imapflow": "^2.10.0",
|
||||
"mailparser": "^3.14.1",
|
||||
"mssql": "^12.7.13",
|
||||
"mysql2": "^3.24.6",
|
||||
"nodemailer": "^6.9.16",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pg": "^8.23.1",
|
||||
"pngjs": "^7.0.0",
|
||||
"sm-crypto": "^0.5.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/mssql": "^12.3.0",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/pg": "^8.23.1",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"typescript": "^5.7.2",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./data";
|
||||
import "./desktop";
|
||||
@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./desktop";
|
||||
import "./basic-encryption";
|
||||
@ -7,7 +7,7 @@ import { PDFDocument } from "pdf-lib";
|
||||
import { PNG } from "pngjs";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import "./desktop";
|
||||
import "./office";
|
||||
import "./pdf";
|
||||
@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./data-extended";
|
||||
@ -4,7 +4,7 @@ import path from "node:path";
|
||||
import { afterAll, describe, expect, it } from "vitest";
|
||||
import { capabilityIds, getHandler } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./data";
|
||||
import "./data-extended";
|
||||
@ -2,7 +2,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./data-table-common";
|
||||
@ -2,21 +2,12 @@ import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type CapabilityHandler } from "../engine/types";
|
||||
import { type ManualOperationSettings, defaultEngine } from "../engine/engine";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
interface ManualOperationSettings {
|
||||
enabled: boolean;
|
||||
randomMove: boolean;
|
||||
randomClick: boolean;
|
||||
randomSleep: boolean;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
}
|
||||
|
||||
const manualOperation: ManualOperationSettings = {
|
||||
enabled: false, randomMove: true, randomClick: true, randomSleep: true, minMs: 1000, maxMs: 3000
|
||||
};
|
||||
// 人为拟真设置本体挂在引擎实例上(任务 1.3),此处委托 defaultEngine。
|
||||
const manualOperation: ManualOperationSettings = defaultEngine.manualOperation;
|
||||
|
||||
function jitter(value: number, amount = 3): number {
|
||||
return Math.max(0, Math.round(value + (Math.random() * amount * 2 - amount)));
|
||||
@ -4,7 +4,7 @@ import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import ExcelJS from "exceljs";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./excel-com";
|
||||
|
||||
@ -3,7 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import ExcelJS from "exceljs";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./index";
|
||||
@ -3,7 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import ExcelJS from "exceljs";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./office";
|
||||
@ -1,10 +1,11 @@
|
||||
import ExcelJS from "exceljs";
|
||||
import path from "node:path";
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
|
||||
// Excel 工作簿共享缓存:同一文件路径在整条流程中复用同一个 ExcelJS 实例。
|
||||
// 此前 office.ts / office-file-extended.ts / excel-common.ts / extended.ts 各自持有
|
||||
// 工作簿实例并整簿写回磁盘,后保存者会把先保存者的修改整体覆盖掉。
|
||||
const cache = new Map<string, ExcelJS.Workbook>();
|
||||
// 缓存本体挂在引擎实例上(任务 1.3),此处委托 defaultEngine。
|
||||
|
||||
function keyOf(filePath: string): string {
|
||||
return path.resolve(filePath);
|
||||
@ -13,22 +14,22 @@ function keyOf(filePath: string): string {
|
||||
/** 按路径取工作簿:命中缓存直接复用,未命中从磁盘读取后登记。 */
|
||||
export async function acquireWorkbook(filePath: string): Promise<ExcelJS.Workbook> {
|
||||
const key = keyOf(filePath);
|
||||
let workbook = cache.get(key);
|
||||
let workbook = defaultEngine.excelCache.get(key);
|
||||
if (!workbook) {
|
||||
workbook = new ExcelJS.Workbook();
|
||||
await workbook.xlsx.readFile(key);
|
||||
cache.set(key, workbook);
|
||||
defaultEngine.excelCache.set(key, workbook);
|
||||
}
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/** 把一个新建/另建的工作簿实例登记为该路径的共享实例。 */
|
||||
export function publishWorkbook(filePath: string, workbook: ExcelJS.Workbook): ExcelJS.Workbook {
|
||||
cache.set(keyOf(filePath), workbook);
|
||||
defaultEngine.excelCache.set(keyOf(filePath), workbook);
|
||||
return workbook;
|
||||
}
|
||||
|
||||
/** 释放路径对应的共享实例(关闭工作簿时调用)。 */
|
||||
export function releaseWorkbook(filePath: string): void {
|
||||
cache.delete(keyOf(filePath));
|
||||
defaultEngine.excelCache.delete(keyOf(filePath));
|
||||
}
|
||||
@ -3,7 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import ExcelJS from "exceljs";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./extended";
|
||||
@ -3,7 +3,7 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./files";
|
||||
@ -3,11 +3,10 @@ import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
|
||||
const flags = globalThis as { __rpaFtpClient?: Client };
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
|
||||
async function requireClient(nodeId: string): Promise<Client> {
|
||||
if (flags.__rpaFtpClient) return flags.__rpaFtpClient;
|
||||
if (defaultEngine.ftpClient) return defaultEngine.ftpClient;
|
||||
throw new RuntimeError("validation_error", "尚未连接 FTP,请先使用 FTP_Connect", { nodeId, componentId: nodeId, recommendation: "先运行 FTP_Connect 建立连接" });
|
||||
}
|
||||
|
||||
@ -27,14 +26,14 @@ register("FTP_Connect", async (ctx, node, args) => {
|
||||
const secure = String(args.ftp_type ?? "").toLowerCase().includes("ftps");
|
||||
const client = new Client(10_000);
|
||||
await client.access({ host, port, secure, user, password });
|
||||
flags.__rpaFtpClient = client;
|
||||
defaultEngine.ftpClient = client;
|
||||
ctx.log("已连接 FTP:" + host + ":" + port, "info", node.id);
|
||||
return { connected: true, host, port };
|
||||
});
|
||||
|
||||
register("FTP_Disconnect", async (ctx, node) => {
|
||||
flags.__rpaFtpClient?.close();
|
||||
flags.__rpaFtpClient = undefined;
|
||||
defaultEngine.ftpClient?.close();
|
||||
defaultEngine.ftpClient = undefined;
|
||||
ctx.log("已断开 FTP 连接", "info", node.id);
|
||||
return { disconnected: true };
|
||||
});
|
||||
39
packages/rpa-core/src/capabilities/index.ts
Normal file
39
packages/rpa-core/src/capabilities/index.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* rpa-core 纯 Node 能力注册入口。
|
||||
*
|
||||
* import 本模块即完成全部纯 Node 能力的副作用注册(与改造前
|
||||
* src/main/capabilities/index.ts 的加载顺序保持一致,别名/重复 id 的
|
||||
* last-wins 语义不变)。依赖 Electron 或宿主模块的能力(human-dialog /
|
||||
* window-extended / ai-service / ai-caps / browser* / web-batch2 / web-crawl)
|
||||
* 留在宿主 src/main/capabilities,阶段 2 通过 HostBridge 迁入。
|
||||
*/
|
||||
import "./control";
|
||||
import "./files";
|
||||
import "./http";
|
||||
import "./data";
|
||||
import "./office";
|
||||
import "./desktop";
|
||||
import "./desktop-uia";
|
||||
import "./time-caps";
|
||||
import "./windows-system";
|
||||
import "./ftp";
|
||||
import "./ocr-caps";
|
||||
import "./pdf";
|
||||
import "./word";
|
||||
import "./word-extended";
|
||||
import "./message";
|
||||
import "./vision";
|
||||
import "./cv-vision";
|
||||
import "./extended";
|
||||
import "./system-extended";
|
||||
import "./office-file-extended";
|
||||
import "./office-aliases";
|
||||
import "./excel-common";
|
||||
import "./excel-com";
|
||||
import "./data-extended";
|
||||
import "./utility-extended";
|
||||
import "./data-table-common";
|
||||
import "./basic-encryption";
|
||||
import "./mail-remaining";
|
||||
import "./remaining-core";
|
||||
import "./common-final";
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./desktop";
|
||||
@ -1,6 +1,6 @@
|
||||
import net from "node:net";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler, hasCapability } from "../engine/registry";
|
||||
import "./message";
|
||||
@ -2,17 +2,16 @@ import path from "node:path";
|
||||
import { promises as fs } from "node:fs";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
|
||||
interface OcrWord { text: string; x: number; y: number; width: number; height: number; confidence: number }
|
||||
interface RapidOcrLine { text: string; mean: number; box?: number[][] }
|
||||
|
||||
let ocrInstance: unknown = null;
|
||||
|
||||
async function getOcr(): Promise<{ detect: (image: string) => Promise<{ texts: RapidOcrLine[] }> }> {
|
||||
if (ocrInstance) return ocrInstance as { detect: (image: string) => Promise<{ texts: RapidOcrLine[] }> };
|
||||
if (defaultEngine.ocrInstance) return defaultEngine.ocrInstance as { detect: (image: string) => Promise<{ texts: RapidOcrLine[] }> };
|
||||
const mod = await import("@gutenye/ocr-node");
|
||||
const instance = await mod.default.create();
|
||||
ocrInstance = instance;
|
||||
defaultEngine.ocrInstance = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./ocr-caps";
|
||||
import "./remaining-core";
|
||||
@ -2,9 +2,8 @@ import { readFile, mkdtemp, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import ExcelJS from "exceljs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { Catalog, CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./office";
|
||||
@ -21,7 +20,8 @@ afterEach(async () => Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursiv
|
||||
|
||||
describe("common Excel aliases", () => {
|
||||
it("registers only real catalog aliases", async () => {
|
||||
const catalog = JSON.parse(await readFile(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../resources/catalog.json"), "utf8")) as Catalog;
|
||||
// vitest 工作目录为仓库根,包内测试统一以 cwd 定位仓库资源
|
||||
const catalog = JSON.parse(await readFile(path.resolve(process.cwd(), "resources/catalog.json"), "utf8")) as Catalog;
|
||||
const ids = new Set(catalog.components.map((item) => item.id));
|
||||
expect(EXCEL_COMMON_ALIASES.length).toBeGreaterThanOrEqual(25);
|
||||
for (const id of EXCEL_COMMON_ALIASES) { expect(ids.has(id), id).toBe(true); expect(getHandler(id), id).toBeTruthy(); }
|
||||
@ -4,7 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./office-file-extended";
|
||||
@ -3,6 +3,7 @@ import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type CapabilityHandler } from "../engine/types";
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
import { acquireWorkbook, publishWorkbook, releaseWorkbook } from "./excel-store";
|
||||
|
||||
interface ExcelHandle {
|
||||
@ -10,7 +11,8 @@ interface ExcelHandle {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const workbooks = new Map<string, ExcelJS.Workbook>();
|
||||
// 句柄 id → 工作簿;本体挂在引擎实例上(任务 1.3),此处委托 defaultEngine。
|
||||
const workbooks = defaultEngine.excelHandles;
|
||||
|
||||
function value(args: Record<string, unknown>, keys: string[], fallback = ""): unknown {
|
||||
for (const key of keys) {
|
||||
@ -5,7 +5,7 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
import { PDFDocument, StandardFonts } from "pdf-lib";
|
||||
import { PNG } from "pngjs";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./pdf";
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./vision";
|
||||
import "./remaining-core";
|
||||
@ -3,6 +3,8 @@ import path from "node:path";
|
||||
import { getHandler, register } from "../engine/registry";
|
||||
import { runFlow } from "../engine/runtime";
|
||||
import { RuntimeError, type CapabilityHandler, type RunContext } from "../engine/types";
|
||||
import { defaultEngine } from "../engine/engine";
|
||||
import { getSubflowSource } from "../engine/host-bridge";
|
||||
import { powershell, psString } from "./desktop";
|
||||
import { findImage } from "./vision";
|
||||
|
||||
@ -24,7 +26,8 @@ function required(value: string, label: string, node: { id: string; componentId:
|
||||
return value;
|
||||
}
|
||||
|
||||
const dbConnections = new Map<string, any>();
|
||||
// 连接 id → 数据库连接;本体挂在引擎实例上(任务 1.3)。
|
||||
const dbConnections = defaultEngine.dbConnections as Map<string, any>;
|
||||
function dbConfig(args: Record<string, unknown>): Record<string, unknown> {
|
||||
const inline = record(args.DatabaseConfig || args.config || args.connectionID);
|
||||
const merged = { ...inline, ...args };
|
||||
@ -67,12 +70,14 @@ register("Database_CloseDBConnect", async (_ctx, _node, args) => {
|
||||
dbConnections.delete(key); return { closed: true };
|
||||
});
|
||||
async function callSubflow(ctx: RunContext, node: { id: string; componentId: string }, args: Record<string, unknown>) {
|
||||
const { listFlows, getFlow } = await import("../flows");
|
||||
// 子流程仓库属宿主职责:经 host-bridge 注入(阶段 2 正式化),包内不 import "../flows"
|
||||
const source = getSubflowSource();
|
||||
if (!source) throw new RuntimeError("validation_error", "子流程源未注入", { nodeId: node.id, componentId: node.componentId, recommendation: "宿主需在启动时调用 setSubflowSource()" });
|
||||
const wanted = text(args, ["module_name", "module", "flow"]);
|
||||
const rows = await listFlows(); const found = rows.find((row) => row.id === wanted || row.name === wanted);
|
||||
const rows = await source.listFlows(); const found = rows.find((row) => row.id === wanted || row.name === wanted);
|
||||
if (!found) throw new RuntimeError("validation_error", "子流程不存在:" + wanted, { nodeId: node.id, componentId: node.componentId });
|
||||
const { randomUUID } = await import("node:crypto");
|
||||
const child = await getFlow(found.id); const inputs = record(args.args || args.input);
|
||||
const child = await source.getFlow(found.id); const inputs = record(args.args || args.input);
|
||||
return await runFlow(child, { runId: randomUUID(), evidenceDir: ctx.evidenceDir, signal: ctx.signal, emit: ctx.emit, log: ctx.log, componentOf: ctx.componentOf, browser: ctx.browser, variables: { ...ctx.vars, ...inputs } });
|
||||
}
|
||||
register("child_modules_v1", async (ctx, node, args) => callSubflow(ctx, node, args));
|
||||
@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./system-extended";
|
||||
@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { Catalog, CatalogComponent, FlowDefinition, FlowNode } from "../../shared/types";
|
||||
import type { Catalog, CatalogComponent, FlowDefinition, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./utility-extended";
|
||||
@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowNode } from "../types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./word";
|
||||
49
packages/rpa-core/src/engine/engine.ts
Normal file
49
packages/rpa-core/src/engine/engine.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import type { Client } from "basic-ftp";
|
||||
import type { Workbook as ExcelJSWorkbook } from "exceljs";
|
||||
|
||||
/** 人为拟真操作的开关与随机参数(desktop 能力的进程级设置)。 */
|
||||
export interface ManualOperationSettings {
|
||||
enabled: boolean;
|
||||
randomMove: boolean;
|
||||
randomClick: boolean;
|
||||
randomSleep: boolean;
|
||||
minMs: number;
|
||||
maxMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 引擎实例状态容器(任务 1.3,R4:模块级全局状态收拢)。
|
||||
*
|
||||
* 折中方案(决策记录 2026-11-04):能力文件的 register() 调用与全局注册表保持不变,
|
||||
* 但 Excel 缓存、数据库连接、FTP 客户端等进程级可变状态统一挂到引擎实例上。
|
||||
* 包内各能力模块通过 defaultEngine 访问;宿主/CLI 可用 createEngine() 产生隔离实例,
|
||||
* 或拿到 defaultEngine 做测试间重置。
|
||||
*/
|
||||
export class RpaEngine {
|
||||
/** excel-store:路径 → ExcelJS 工作簿共享缓存 */
|
||||
readonly excelCache = new Map<string, ExcelJSWorkbook>();
|
||||
/** office.ts:Excel 句柄 id → 工作簿 */
|
||||
readonly excelHandles = new Map<string, ExcelJSWorkbook>();
|
||||
/** remaining-core:连接 id → 数据库连接(mysql2/pg/mssql) */
|
||||
readonly dbConnections = new Map<string, unknown>();
|
||||
/** ftp.ts:当前 FTP 连接 */
|
||||
ftpClient?: Client;
|
||||
/** ocr-caps.ts:懒加载的 OCR 实例 */
|
||||
ocrInstance?: unknown;
|
||||
/** desktop.ts:人为拟真操作设置 */
|
||||
manualOperation: ManualOperationSettings = {
|
||||
enabled: false,
|
||||
randomMove: true,
|
||||
randomClick: true,
|
||||
randomSleep: true,
|
||||
minMs: 1000,
|
||||
maxMs: 3000
|
||||
};
|
||||
}
|
||||
|
||||
export function createEngine(): RpaEngine {
|
||||
return new RpaEngine();
|
||||
}
|
||||
|
||||
/** 能力模块内部使用的默认引擎实例(与全局注册表配套的进程级单例)。 */
|
||||
export const defaultEngine = createEngine();
|
||||
22
packages/rpa-core/src/engine/host-bridge.ts
Normal file
22
packages/rpa-core/src/engine/host-bridge.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { FlowDefinition } from "../types";
|
||||
|
||||
/**
|
||||
* 宿主桥(HostBridge 候选,阶段 2 正式化接口)。
|
||||
*
|
||||
* 包内个别能力需要宿主数据(目前仅子流程仓库),通过显式 setter 注入,
|
||||
* 包本身不得 import 宿主模块。宿主启动时调用 setSubflowSource()。
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowDefinition } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowDefinition } from "../types";
|
||||
import { register } from "./registry";
|
||||
import { runFlow } from "./runtime";
|
||||
|
||||
@ -10,7 +10,7 @@ import type {
|
||||
FlowEdge,
|
||||
FlowNode,
|
||||
RunEvent
|
||||
} from "../../shared/types";
|
||||
} from "../types";
|
||||
import "../capabilities";
|
||||
import { register } from "./registry";
|
||||
import { runFlow } from "./runtime";
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FlowDefinition, FlowNode, RunError } from "../../shared/types";
|
||||
import type { FlowDefinition, FlowNode, RunError } from "../types";
|
||||
import { evaluate, evaluateCondition, interpolate, toNumber } from "./expression";
|
||||
import { getHandler } from "./registry";
|
||||
import {
|
||||
@ -1,4 +1,4 @@
|
||||
import type { CatalogComponent, FlowDefinition, FlowNode, RunError, RunEvent } from "../../shared/types";
|
||||
import type { CatalogComponent, FlowDefinition, FlowNode, RunError, RunEvent } from "../types";
|
||||
|
||||
export interface BrowserLike {
|
||||
navigate(url: string): Promise<{ url: string; title: string }>;
|
||||
43
packages/rpa-core/src/index.ts
Normal file
43
packages/rpa-core/src/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @rpa/core 包入口。
|
||||
*
|
||||
* 只导出引擎 API 与类型,不做能力注册副作用;
|
||||
* 纯 Node 能力通过 `@rpa/core/capabilities` 子路径按需拉起(保持与 demo 宿主
|
||||
* src/main/capabilities/index.ts 相同的副作用注册模型,见任务 4.2 再显式化)。
|
||||
*/
|
||||
export { createEngine, defaultEngine, RpaEngine, type ManualOperationSettings } from "./engine/engine";
|
||||
export { register, getHandler, hasCapability, capabilityIds, capabilityCount } from "./engine/registry";
|
||||
export { runFlow } from "./engine/runtime";
|
||||
export { RuntimeError, BreakSignal, ContinueSignal, StopSignal, asRuntimeError } from "./engine/types";
|
||||
export { setSubflowSource, getSubflowSource, type SubflowSource } from "./engine/host-bridge";
|
||||
// PowerShell 助手(阶段 3 将移入 driver/windows/):宿主留守能力(window-extended 等)经此引用
|
||||
export { powershell, psString } from "./capabilities/desktop";
|
||||
export type {
|
||||
BrowserLike,
|
||||
BrowserTabInfo,
|
||||
BrowserElementReference,
|
||||
BrowserElementInfo,
|
||||
BrowserSelectOption,
|
||||
BrowserSelectState,
|
||||
BrowserSimilarElementOptions,
|
||||
BrowserElementRegion,
|
||||
RunContext,
|
||||
CapabilityHandler,
|
||||
NodeState,
|
||||
RunResult
|
||||
} from "./engine/types";
|
||||
export type {
|
||||
CatalogWidget,
|
||||
CatalogField,
|
||||
CatalogOutput,
|
||||
CatalogComponent,
|
||||
CatalogGroup,
|
||||
Catalog,
|
||||
FlowVariable,
|
||||
FlowNodeOptions,
|
||||
FlowNode,
|
||||
FlowEdge,
|
||||
FlowDefinition,
|
||||
RunError,
|
||||
RunEvent
|
||||
} from "./types";
|
||||
187
packages/rpa-core/src/types.ts
Normal file
187
packages/rpa-core/src/types.ts
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* rpa-core 领域类型(任务 1.2,类型单一源)
|
||||
*
|
||||
* 从 src/shared/types 拆出:与流程/目录/运行相关、引擎必须依赖的模型归包所有。
|
||||
* 宿主专属类型(AppInfo / AppSettings / FlowSummary / VisionLab* 等)留在 src/shared/types,
|
||||
* 该文件通过 type-only re-export 引用本文件,保证全仓不出现两份定义。
|
||||
*/
|
||||
|
||||
export type CatalogWidget =
|
||||
| "text"
|
||||
| "textarea"
|
||||
| "number"
|
||||
| "select"
|
||||
| "radio"
|
||||
| "boolean"
|
||||
| "file"
|
||||
| "folder"
|
||||
| "element"
|
||||
| "code"
|
||||
| "secret"
|
||||
| "region"
|
||||
| "hidden";
|
||||
|
||||
export interface CatalogField {
|
||||
key: string;
|
||||
label: string;
|
||||
widget: CatalogWidget;
|
||||
type: "string" | "number" | "boolean" | "object";
|
||||
displayType: string;
|
||||
required: boolean;
|
||||
defaultValue: string;
|
||||
placeholder: string;
|
||||
description: string;
|
||||
advanced: boolean;
|
||||
valueTypes: string[];
|
||||
/** 下拉/单选选项:字符串(显示=存储值)或 { label, value }(显示与存储分离) */
|
||||
options?: Array<string | { label: string; value: string }>;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface CatalogOutput {
|
||||
label: string;
|
||||
key: string;
|
||||
suggestedName: string;
|
||||
valueTypes: string[];
|
||||
fields: Array<{ key: string; label: string; defaultValue: string; valueTypes: string[] }>;
|
||||
}
|
||||
|
||||
export interface CatalogComponent {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: "vendor" | "native" | "trigger";
|
||||
group: string;
|
||||
categories: string[];
|
||||
categorySignature: string;
|
||||
description: string;
|
||||
messageTemplate: string;
|
||||
baseType: string;
|
||||
functionName: string;
|
||||
functionCalls: Array<{ function: string; args: string[]; assignTo: string }>;
|
||||
version: string;
|
||||
tags: string[];
|
||||
platformTag: string | null;
|
||||
icon: string;
|
||||
risk: "low" | "high";
|
||||
fields: CatalogField[];
|
||||
output: CatalogOutput;
|
||||
slots: string[];
|
||||
nodeKind?: string;
|
||||
/** 同名旧版本 / 重复别名指向的保留能力 ID。 */
|
||||
duplicateOf?: string;
|
||||
}
|
||||
|
||||
export interface CatalogGroup {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface Catalog {
|
||||
schemaVersion: number;
|
||||
generatedAt: string;
|
||||
counts: {
|
||||
total: number;
|
||||
vendor: number;
|
||||
native: number;
|
||||
triggers: number;
|
||||
fields: number;
|
||||
};
|
||||
groups: CatalogGroup[];
|
||||
components: CatalogComponent[];
|
||||
}
|
||||
|
||||
export interface FlowVariable {
|
||||
name: string;
|
||||
type: string;
|
||||
defaultValue?: string;
|
||||
description?: string;
|
||||
scope?: "flow" | "local";
|
||||
}
|
||||
|
||||
export interface FlowNodeOptions {
|
||||
outputVar?: string;
|
||||
delayBeforeMs?: number;
|
||||
delayAfterMs?: number;
|
||||
timeoutMs?: number;
|
||||
retryCount?: number;
|
||||
retryIntervalMs?: number;
|
||||
skipOnError?: boolean;
|
||||
logException?: boolean;
|
||||
errorRouteEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface FlowNode {
|
||||
id: string;
|
||||
componentId: string;
|
||||
label?: string;
|
||||
position: { x: number; y: number };
|
||||
values: Record<string, unknown>;
|
||||
options: FlowNodeOptions;
|
||||
parentId?: string | null;
|
||||
slot?: string;
|
||||
enabled?: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export interface FlowEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
sourceHandle?: string | null;
|
||||
targetHandle?: string | null;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface FlowDefinition {
|
||||
schemaVersion: 1;
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
trigger?: {
|
||||
type: "manual" | "schedule" | "file";
|
||||
label: string;
|
||||
};
|
||||
variables: FlowVariable[];
|
||||
nodes: FlowNode[];
|
||||
edges: FlowEdge[];
|
||||
meta: {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
appVersion: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RunError {
|
||||
code: string;
|
||||
reason: string;
|
||||
recommendation?: string;
|
||||
location?: { flowId?: string; nodeId?: string; componentId?: string; arg?: string };
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export interface RunEvent {
|
||||
type:
|
||||
| "run.started"
|
||||
| "node.started"
|
||||
| "node.log"
|
||||
| "node.finished"
|
||||
| "run.finished"
|
||||
| "run.total";
|
||||
runId: string;
|
||||
nodeId?: string;
|
||||
level?: "info" | "warn" | "error";
|
||||
message?: string;
|
||||
status?: string;
|
||||
output?: unknown;
|
||||
error?: RunError;
|
||||
durationMs?: number;
|
||||
evidence?: string[];
|
||||
attempt?: number;
|
||||
startedAt?: string;
|
||||
nodeLabel?: string;
|
||||
total?: number;
|
||||
}
|
||||
16
packages/rpa-core/tsconfig.json
Normal file
16
packages/rpa-core/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
8
packages/rpa-core/vitest.config.ts
Normal file
8
packages/rpa-core/vitest.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
// 与根配置一致:桌面自动化冒烟测试共用同一物理桌面,串行执行防焦点竞争。
|
||||
fileParallelism: false
|
||||
}
|
||||
});
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-11-04T03:53:23.689Z",
|
||||
"generatedAt": "2026-11-04T04:34:06.782Z",
|
||||
"count": 909,
|
||||
"ids": [
|
||||
"App_GetPid",
|
||||
|
||||
@ -5,8 +5,8 @@ import { afterEach, describe, expect, it } from "vitest";
|
||||
import { buildSuite, bundleRoot } from "./app-system-test-flows.mjs";
|
||||
import { installBundle, upgradeBundle, makeReport, caseResults } from "./app-system-tests.mjs";
|
||||
import { applyCatalogOverrides } from "./catalog-overrides.mjs";
|
||||
import { hasCapability } from "../src/main/capabilities/index.ts";
|
||||
import { runFlow } from "../src/main/engine/runtime.ts";
|
||||
import { hasCapability, runFlow } from "@rpa/core";
|
||||
import "../src/main/capabilities/index.ts";
|
||||
import { validateFlow } from "../src/renderer/src/validation/flowValidation.ts";
|
||||
|
||||
const containers = new Set(["logic_control_try_catch", "logic_control_if_else"]);
|
||||
@ -58,7 +58,7 @@ describe("应用与系统测试流程包", () => {
|
||||
const options = component.fields.find(field => field.key === "into_type").options;
|
||||
expect(options.length).toBeGreaterThanOrEqual(4);
|
||||
for (const option of options) {
|
||||
const output = await import("../src/main/engine/registry.ts").then(({ getHandler }) => getHandler(id)({}, { id: "type-test", componentId: id }, { val: 42, into_type: option.value }, component));
|
||||
const output = await import("@rpa/core").then(({ getHandler }) => getHandler(id)({}, { id: "type-test", componentId: id }, { val: 42, into_type: option.value }, component));
|
||||
if (id === "Basic_JudgeDataType") expect(typeof output).toBe("boolean");
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { execFile, spawn, type ChildProcess } from "node:child_process";
|
||||
import { promises as fs } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
import { ExternalTransport } from "./transport";
|
||||
|
||||
/**
|
||||
|
||||
@ -30,7 +30,7 @@ import {
|
||||
type BrowserSelectState,
|
||||
type BrowserSimilarElementOptions,
|
||||
type BrowserTabInfo
|
||||
} from "../engine/types";
|
||||
} from "@rpa/core";
|
||||
|
||||
function assertUrl(raw: string): URL {
|
||||
let url: URL;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { BrowserWindow } from "electron";
|
||||
import WebSocket from "ws";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
|
||||
/**
|
||||
* CDP 传输层抽象。
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
import { chatCompletion, requireAiConfig } from "./ai-service";
|
||||
|
||||
function inputText(args: Record<string, unknown>, nodeId: string): string {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { loadSettings } from "../settings";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
|
||||
export type ChatContentPart =
|
||||
| { type: "text"; text: string }
|
||||
|
||||
@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import {
|
||||
RuntimeError,
|
||||
type BrowserElementInfo,
|
||||
@ -10,7 +10,7 @@ import {
|
||||
type BrowserLike,
|
||||
type BrowserSelectState,
|
||||
type RunContext
|
||||
} from "../engine/types";
|
||||
} from "@rpa/core";
|
||||
import { BROWSER_COMMON_IDS } from "./browser-common";
|
||||
|
||||
const component = {} as CatalogComponent;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserElementInfo, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserElementInfo, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
|
||||
const GET_ELEMENT_IDS = ["Element_GetElement", "Element_GetElementV1", "Element_GetElementV2", "Element_GetElementNew"];
|
||||
const GET_INFO_IDS = ["Element_GetElementInfo", "Element_GetElementInfo_V1"];
|
||||
|
||||
@ -2,8 +2,8 @@ import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { BrowserLike, RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { BrowserLike, RunContext } from "@rpa/core";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import { BROWSER_CONTROL_IDS } from "./browser-control";
|
||||
|
||||
const component = {} as CatalogComponent; const node = (id: string): FlowNode => ({ id, componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import path from "node:path";
|
||||
import { promises as fs } from "node:fs";
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
|
||||
async function session(ctx: RunContext): Promise<BrowserLike> { if (!ctx.browser) ctx.browser = await browserSession(); return ctx.browser; }
|
||||
function selector(args: Record<string, unknown>): unknown { const value = args.uiSelector ?? args.download_selector ?? args.selector; if (value === undefined || value === null || value === "") throw new RuntimeError("validation_error", "元素选择器为空"); return value; }
|
||||
|
||||
@ -2,8 +2,8 @@ import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserElementRegion, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserElementRegion, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
import { BROWSER_DOM_IDS } from "./browser-dom";
|
||||
|
||||
const component = {} as CatalogComponent;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
|
||||
export const BROWSER_DOM_IDS = [
|
||||
"Element_GetAttribute", "Element_GetAttribute_v1", "Element_GetAttribute_v2", "Element_GetAttribute_v3",
|
||||
|
||||
@ -2,8 +2,8 @@ import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
import "./browser-extended";
|
||||
|
||||
const EXTENDED_IDS = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
|
||||
const CLICK_IDS = ["Element_Click_V1", "Element_Click_V2", "Element_Click_V4", "Element_Click_V5"];
|
||||
const TEXT_INPUT_IDS = ["Element_SetValue_V1", "Element_SetValue_V2", "Element_SetValue_V3", "Element_SetValue_V5"];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
import "./browser";
|
||||
|
||||
const component = {} as CatalogComponent;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
import { browserSession, externalBrowserSession } from "../browser/session";
|
||||
import type { BrowserLike, RunContext } from "../engine/types";
|
||||
import type { BrowserLike, RunContext } from "@rpa/core";
|
||||
|
||||
async function session(ctx: RunContext): Promise<BrowserLike> {
|
||||
if (!ctx.browser) ctx.browser = await browserSession();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Catalog } from "../../shared/types";
|
||||
import { hasCapability } from "../engine/registry";
|
||||
import { hasCapability } from "@rpa/core";
|
||||
import "./index";
|
||||
|
||||
const planned = [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { Catalog, CatalogComponent, CatalogField } from "../../shared/types";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hasCapability } from "../engine/registry";
|
||||
import { hasCapability } from "@rpa/core";
|
||||
import "./index";
|
||||
|
||||
const widgets = new Set(["text","textarea","number","select","radio","boolean","file","folder","element","code","secret","region","hidden"]);
|
||||
|
||||
@ -5,9 +5,9 @@ import path from "node:path";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import "./desktop"; import "./pdf"; import "./system-extended"; import "./window-extended"; import "./common-final";
|
||||
import type { RunContext } from "@rpa/core";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import "@rpa/core/capabilities"; import "./window-extended";
|
||||
|
||||
const dirs: string[] = []; const context = { vars: {} } as RunContext; const component = {} as CatalogComponent;
|
||||
const node = (id: string): FlowNode => ({ id, componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { BrowserWindow } from "electron";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError } from "@rpa/core";
|
||||
|
||||
interface FormFieldSpec { key?: string; name?: string; label?: string; type?: string; value?: string; options?: unknown[] }
|
||||
|
||||
|
||||
@ -1,44 +1,21 @@
|
||||
import "./control";
|
||||
import "./files";
|
||||
import "./http";
|
||||
import "./data";
|
||||
/**
|
||||
* 宿主能力注册入口。
|
||||
*
|
||||
* 纯 Node 能力已迁入 @rpa/core(packages/rpa-core),第一行 import 拉起包内全部注册;
|
||||
* 此处只保留依赖 Electron / 宿主模块的能力,阶段 2 通过 HostBridge 逐步迁入。
|
||||
* 导出顺序整体保持改造前 index.ts 的注册顺序(别名/重复 id last-wins 语义不变)。
|
||||
*/
|
||||
import "@rpa/core/capabilities";
|
||||
import "./browser";
|
||||
import "./browser-extended";
|
||||
import "./browser-dom";
|
||||
import "./browser-common";
|
||||
import "./browser-control";
|
||||
import "./office";
|
||||
import "./desktop";
|
||||
import "./desktop-uia";
|
||||
import "./web-batch2";
|
||||
import "./web-crawl";
|
||||
import "./human-dialog";
|
||||
import "./time-caps";
|
||||
import "./windows-system";
|
||||
import "./ftp";
|
||||
import "./ai-service";
|
||||
import "./ai-caps";
|
||||
import "./ocr-caps";
|
||||
import "./pdf";
|
||||
import "./word";
|
||||
import "./word-extended";
|
||||
import "./message";
|
||||
import "./vision";
|
||||
import "./cv-vision";
|
||||
import "./extended";
|
||||
import "./system-extended";
|
||||
import "./office-file-extended";
|
||||
import "./office-aliases";
|
||||
import "./excel-common";
|
||||
import "./excel-com";
|
||||
import "./window-extended";
|
||||
import "./data-extended";
|
||||
import "./utility-extended";
|
||||
import "./data-table-common";
|
||||
import "./basic-encryption";
|
||||
import "./mail-remaining";
|
||||
import "./remaining-core";
|
||||
import "./common-final";
|
||||
|
||||
export { capabilityCount, capabilityIds, getHandler, hasCapability, register } from "../engine/registry";
|
||||
|
||||
export { capabilityCount, capabilityIds, getHandler, hasCapability, register } from "@rpa/core";
|
||||
|
||||
@ -2,8 +2,8 @@ import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import type { RunContext } from "@rpa/core";
|
||||
import "./window-extended";
|
||||
|
||||
const run = promisify(execFile);
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { createServer, type IncomingMessage, type ServerResponse, type Server } from "node:http";
|
||||
import net from "node:net";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import { getHandler } from "@rpa/core";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./http";
|
||||
import "./message";
|
||||
import "./common-final";
|
||||
import type { RunContext } from "@rpa/core";
|
||||
import "@rpa/core/capabilities";
|
||||
import "./ai-caps";
|
||||
|
||||
const ctx = {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { capabilityCount, hasCapability } from "../engine/registry";
|
||||
import { capabilityCount, hasCapability } from "@rpa/core";
|
||||
import "./index";
|
||||
|
||||
const selected = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { browserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type BrowserTabInfo, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type BrowserTabInfo, type RunContext } from "@rpa/core";
|
||||
|
||||
/** 优先使用流程中「打开网页」已建立的浏览器会话(含外部 Edge/Chrome),否则退回内置 */
|
||||
async function currentSession(ctx: RunContext): Promise<BrowserLike> {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { browserSession, externalBrowserSession } from "../browser/session";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "../engine/types";
|
||||
import { register } from "@rpa/core";
|
||||
import { RuntimeError, type BrowserLike, type RunContext } from "@rpa/core";
|
||||
|
||||
/**
|
||||
* 采集/监听组件的浏览器会话解析:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user