@qing3a/flow-rpa-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/atomic.d.ts +9 -0
  2. package/dist/atomic.js +32 -0
  3. package/dist/atomic.js.map +1 -0
  4. package/dist/bench.d.ts +57 -0
  5. package/dist/bench.js +112 -0
  6. package/dist/bench.js.map +1 -0
  7. package/dist/cdp/client.d.ts +70 -0
  8. package/dist/cdp/client.js +272 -0
  9. package/dist/cdp/client.js.map +1 -0
  10. package/dist/cdp/page.d.ts +93 -0
  11. package/dist/cdp/page.js +411 -0
  12. package/dist/cdp/page.js.map +1 -0
  13. package/dist/executor.d.ts +122 -0
  14. package/dist/executor.js +399 -0
  15. package/dist/executor.js.map +1 -0
  16. package/dist/export.d.ts +128 -0
  17. package/dist/export.js +234 -0
  18. package/dist/export.js.map +1 -0
  19. package/dist/gate.d.ts +56 -0
  20. package/dist/gate.js +144 -0
  21. package/dist/gate.js.map +1 -0
  22. package/dist/hello.d.ts +5 -0
  23. package/dist/hello.js +13 -0
  24. package/dist/hello.js.map +1 -0
  25. package/dist/human.d.ts +162 -0
  26. package/dist/human.js +506 -0
  27. package/dist/human.js.map +1 -0
  28. package/dist/index.d.ts +20 -0
  29. package/dist/index.js +16 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/learning.d.ts +82 -0
  32. package/dist/learning.js +240 -0
  33. package/dist/learning.js.map +1 -0
  34. package/dist/mask.d.ts +32 -0
  35. package/dist/mask.js +70 -0
  36. package/dist/mask.js.map +1 -0
  37. package/dist/platform.d.ts +66 -0
  38. package/dist/platform.js +215 -0
  39. package/dist/platform.js.map +1 -0
  40. package/dist/record.d.ts +48 -0
  41. package/dist/record.js +42 -0
  42. package/dist/record.js.map +1 -0
  43. package/dist/schema.d.ts +130 -0
  44. package/dist/schema.js +93 -0
  45. package/dist/schema.js.map +1 -0
  46. package/dist/simprofile.d.ts +94 -0
  47. package/dist/simprofile.js +247 -0
  48. package/dist/simprofile.js.map +1 -0
  49. package/dist/storage.d.ts +63 -0
  50. package/dist/storage.js +138 -0
  51. package/dist/storage.js.map +1 -0
  52. package/dist/validate.d.ts +37 -0
  53. package/dist/validate.js +108 -0
  54. package/dist/validate.js.map +1 -0
  55. package/package.json +32 -0
@@ -0,0 +1,247 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ /**
5
+ * 模拟参数差异化层(sim profile)—— 从 hr-rpa-mcp `src/core/simprofile.mjs`
6
+ * 完整移植(Rust `simprofile.rs` 已 1:1 复刻原版,TS 直接按 Rust 语义翻译,三者一致)。
7
+ *
8
+ * 目的:让每台机器/每个实例的拟人行为参数「基准」互不相同。首次运行时用机器指纹
9
+ * 生成稳定的 ±15% 乘性扰动,缓存到本地 sim-profile.json,之后每次运行读同一份缓存。
10
+ *
11
+ * 原则:
12
+ * - BASE_SIM 的值 = human.mjs 原硬编码值(勿改,作基准)
13
+ * - 只扰动「行为风格」参数;不扰动「结构边界」
14
+ * - 纯本地,无服务器依赖
15
+ */
16
+ /** 扰动幅度(±百分比) */
17
+ export const PERTURB_PCT = 0.15;
18
+ /** 缓存格式版本:BASE_SIM 结构变化时 bump,旧缓存失效重新生成 */
19
+ const PROFILE_VERSION = 1;
20
+ /**
21
+ * 确定性 PRNG:xorshift32(对应 hr-rpa-mcp `createRng`)。
22
+ * `t ^= t<<13; t ^= t>>>17; t ^= t<<5; t>>>0; return (t%1e6)/1e6`
23
+ */
24
+ export class XorShift32 {
25
+ s;
26
+ constructor(seed) {
27
+ // JS createRng: `t = seed >>> 0 || 1`(seed 0 → 1)
28
+ this.s = seed > 0 ? seed >>> 0 : 1;
29
+ }
30
+ /** 无种子默认随机(等价 Rust `XorShift32::time_seeded`):取高精度时钟低 32 位作种子 */
31
+ static timeSeeded() {
32
+ const t = Number(process.hrtime.bigint() & 0xffffffffn);
33
+ return new XorShift32((t ^ 0x5bd1e995) >>> 0);
34
+ }
35
+ next() {
36
+ let t = this.s;
37
+ t ^= t << 13;
38
+ t ^= t >>> 17;
39
+ t ^= t << 5;
40
+ t >>>= 0;
41
+ this.s = t;
42
+ return (t % 1_000_000) / 1_000_000;
43
+ }
44
+ }
45
+ /**
46
+ * mulberry32:确定性 PRNG(hr-rpa-mcp simprofile 的机器指纹扰动用)。
47
+ * 全部为 32 位有符号整数运算(|0 / Math.imul / >>> 0),TS 用 >>>0 保证无符号。
48
+ */
49
+ export function mulberry32(a) {
50
+ let state = a >>> 0;
51
+ return function next() {
52
+ state = (state + 0x6d2b79f5) >>> 0;
53
+ let t = Math.imul(state ^ (state >>> 15), 1 | state);
54
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
55
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
56
+ };
57
+ }
58
+ export const BASE_SIM = {
59
+ // mouseMove
60
+ jitter: 2.5,
61
+ moveMedianMs: 16,
62
+ moveSigma: 0.35,
63
+ startOffsetRange: [100, 200],
64
+ // mouseClick
65
+ clickJitter: 1.8,
66
+ hoverRangeMs: [150, 400],
67
+ overshootProbability: 0.3,
68
+ overshootPx: [2, 6],
69
+ overshootMedianMs: 18,
70
+ overshootSigma: 0.3,
71
+ pressMedianMs: 120,
72
+ pressSigma: 0.45,
73
+ doubleClickRangeMs: [90, 180],
74
+ // mouseWheel
75
+ wheelRatioRange: [0.7, 0.85],
76
+ wheelInertiaStepsRange: [6, 12],
77
+ wheelUniformStepsRange: [3, 6],
78
+ wheelIntervalMs: [20, 60],
79
+ // mouseDrag
80
+ dragJitter: 2.5,
81
+ pressDelayMs: [100, 300],
82
+ releaseDelayMs: [50, 150],
83
+ dragPreMedianMs: 30,
84
+ dragPreSigma: 0.3,
85
+ dragMoveMedianMs: 40,
86
+ dragMoveSigma: 0.4,
87
+ // keyboardInput
88
+ wpmRange: [180, 280],
89
+ errorRate: 0.02,
90
+ thinkPauseChance: 0.05,
91
+ thinkPauseRangeMs: [500, 1500],
92
+ finalPauseMs: 200,
93
+ typeSigma: 0.35,
94
+ typeErrSigma: 0.4,
95
+ typeBackspaceSigma: 0.3,
96
+ // randomClickIn
97
+ randomClickMaxClicks: 2,
98
+ randomClickIntervalMs: 1000,
99
+ };
100
+ /* ---------------- 机器指纹 + 扰动 ---------------- */
101
+ /** 机器指纹 → 稳定 seed:用户名/主机名/域/工作目录(不含绝对时间,同机稳定) */
102
+ export function machineSeed() {
103
+ const s = [
104
+ process.env.USERNAME || process.env.USER || 'u',
105
+ process.env.COMPUTERNAME || process.env.HOSTNAME || 'h',
106
+ process.env.USERDOMAIN || '',
107
+ process.cwd(),
108
+ ].join('|');
109
+ return createHash('sha256').update(s).digest().readUInt32LE(0);
110
+ }
111
+ const r3 = (v) => Math.round(v * 1000) / 1000;
112
+ /** 单点数值:乘性 ±15% */
113
+ function pertNum(v, rng) {
114
+ return r3(v * (1 + (rng() * 2 - 1) * PERTURB_PCT));
115
+ }
116
+ /** 区间:两端各自乘性 ±15%,保留 3 位小数 */
117
+ function pertRange([a, b], rng) {
118
+ const fa = 1 + (rng() * 2 - 1) * PERTURB_PCT;
119
+ const fb = 1 + (rng() * 2 - 1) * PERTURB_PCT;
120
+ const na = r3(a * fa);
121
+ const nb = r3(b * fb);
122
+ return [Math.min(na, nb), Math.max(na, nb)];
123
+ }
124
+ /** 概率:乘性 ±15%,clamp 到 [0,1] */
125
+ function pertProb(v, rng) {
126
+ const x = v * (1 + (rng() * 2 - 1) * PERTURB_PCT);
127
+ return r3(Math.max(0, Math.min(1, x)));
128
+ }
129
+ /** 由 BASE_SIM 生成一份扰动后的 profile(对应 JS `perturbBase`) */
130
+ export function perturbBase(rng) {
131
+ const b = BASE_SIM;
132
+ const p = {
133
+ jitter: pertNum(b.jitter, rng),
134
+ moveMedianMs: pertNum(b.moveMedianMs, rng),
135
+ moveSigma: pertNum(b.moveSigma, rng),
136
+ startOffsetRange: pertRange(b.startOffsetRange, rng),
137
+ clickJitter: pertNum(b.clickJitter, rng),
138
+ hoverRangeMs: pertRange(b.hoverRangeMs, rng),
139
+ overshootProbability: pertProb(b.overshootProbability, rng),
140
+ overshootPx: pertRange(b.overshootPx, rng),
141
+ overshootMedianMs: pertNum(b.overshootMedianMs, rng),
142
+ overshootSigma: pertNum(b.overshootSigma, rng),
143
+ pressMedianMs: pertNum(b.pressMedianMs, rng),
144
+ pressSigma: pertNum(b.pressSigma, rng),
145
+ doubleClickRangeMs: pertRange(b.doubleClickRangeMs, rng),
146
+ wheelRatioRange: pertRange(b.wheelRatioRange, rng),
147
+ wheelInertiaStepsRange: pertRange(b.wheelInertiaStepsRange, rng),
148
+ wheelUniformStepsRange: pertRange(b.wheelUniformStepsRange, rng),
149
+ wheelIntervalMs: pertRange(b.wheelIntervalMs, rng),
150
+ dragJitter: pertNum(b.dragJitter, rng),
151
+ pressDelayMs: pertRange(b.pressDelayMs, rng),
152
+ releaseDelayMs: pertRange(b.releaseDelayMs, rng),
153
+ dragPreMedianMs: pertNum(b.dragPreMedianMs, rng),
154
+ dragPreSigma: pertNum(b.dragPreSigma, rng),
155
+ dragMoveMedianMs: pertNum(b.dragMoveMedianMs, rng),
156
+ dragMoveSigma: pertNum(b.dragMoveSigma, rng),
157
+ wpmRange: pertRange(b.wpmRange, rng),
158
+ errorRate: pertProb(b.errorRate, rng),
159
+ thinkPauseChance: pertProb(b.thinkPauseChance, rng),
160
+ thinkPauseRangeMs: pertRange(b.thinkPauseRangeMs, rng),
161
+ finalPauseMs: pertNum(b.finalPauseMs, rng),
162
+ typeSigma: pertNum(b.typeSigma, rng),
163
+ typeErrSigma: pertNum(b.typeErrSigma, rng),
164
+ typeBackspaceSigma: pertNum(b.typeBackspaceSigma, rng),
165
+ randomClickMaxClicks: pertNum(b.randomClickMaxClicks, rng),
166
+ randomClickIntervalMs: pertNum(b.randomClickIntervalMs, rng),
167
+ };
168
+ return p;
169
+ }
170
+ /**
171
+ * 加载本机模拟参数:首次生成扰动并缓存,之后读缓存(同机稳定)。
172
+ * 任何异常都回退到 BASE_SIM(未扰动基准),绝不阻断主流程。
173
+ * 等价 Rust `load_sim_profile(profile_file)`。
174
+ */
175
+ export function loadSimProfile(profileFile) {
176
+ // 1. 命中缓存直接返回(__v 校验)
177
+ try {
178
+ const raw = readFileSync(profileFile, 'utf8');
179
+ const v = JSON.parse(raw);
180
+ if (v && typeof v === 'object' && v.__v === PROFILE_VERSION) {
181
+ const profile = parseProfile(v);
182
+ if (profile)
183
+ return { __v: PROFILE_VERSION, profile };
184
+ }
185
+ }
186
+ catch {
187
+ // 缓存损坏 → 走重建分支
188
+ }
189
+ // 2. 首次生成 + 写缓存
190
+ try {
191
+ const profile = perturbBase(mulberry32(machineSeed()));
192
+ const withV = { __v: PROFILE_VERSION, ...profile };
193
+ mkdirSync(dirname(profileFile), { recursive: true });
194
+ writeFileSync(profileFile, JSON.stringify(withV, null, 2), 'utf8');
195
+ return { __v: PROFILE_VERSION, profile };
196
+ }
197
+ catch {
198
+ return { __v: PROFILE_VERSION, profile: { ...BASE_SIM } };
199
+ }
200
+ }
201
+ /** 从任意对象解析 SimProfile(字段缺失/类型不符 → null,等价 Rust from_value 的 ok()) */
202
+ export function parseProfile(v) {
203
+ if (!v || typeof v !== 'object')
204
+ return null;
205
+ const o = v;
206
+ // 区间字段(值为两元素数组)列表:单点 number 循环跳过,由下方统一校验
207
+ const rangeKeys = [
208
+ 'startOffsetRange',
209
+ 'hoverRangeMs',
210
+ 'overshootPx',
211
+ 'doubleClickRangeMs',
212
+ 'wheelRatioRange',
213
+ 'wheelInertiaStepsRange',
214
+ 'wheelUniformStepsRange',
215
+ 'wheelIntervalMs',
216
+ 'pressDelayMs',
217
+ 'releaseDelayMs',
218
+ 'thinkPauseRangeMs',
219
+ 'wpmRange',
220
+ ];
221
+ const need = Object.keys(BASE_SIM);
222
+ const out = {};
223
+ const rangeKeySet = new Set(rangeKeys);
224
+ for (const k of need) {
225
+ if (rangeKeySet.has(k))
226
+ continue;
227
+ const val = o[k];
228
+ if (typeof val !== 'number')
229
+ return null;
230
+ // out[k] 的目标类型对区间字段是 [number, number];单点字段是 number。
231
+ // 运行时这里只写 number 值(区间字段稍后整体覆写),断言交给区间循环。
232
+ out[k] = val;
233
+ }
234
+ for (const k of rangeKeys) {
235
+ const val = o[k];
236
+ if (!Array.isArray(val) || val.length !== 2 || typeof val[0] !== 'number' || typeof val[1] !== 'number') {
237
+ return null;
238
+ }
239
+ out[k] = [val[0], val[1]];
240
+ }
241
+ return out;
242
+ }
243
+ /** 默认缓存路径:由 app 层注入数据目录(测试传临时文件,隔离真实拟人参数) */
244
+ export function defaultProfileFile(dataDir) {
245
+ return `${dataDir.replace(/[\\/]+$/, '')}/sim-profile.json`;
246
+ }
247
+ //# sourceMappingURL=simprofile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simprofile.js","sourceRoot":"","sources":["../src/simprofile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { ExtractSink } from './executor.js';
2
+ import type { RunRecord } from './record.js';
3
+ import type { DomSnapshot } from './export.js';
4
+ /**
5
+ * 执行记录存储 —— run.json 落盘 + 上传台账(移植自 Rust `storage.rs` + A5 原子写 + D-E2 落盘)。
6
+ *
7
+ * 本地存储目录:
8
+ * runs/<run_id>.json # 执行记录(run.json)
9
+ * snapshots/<run_id>.json # DOM 快照(D-S2:单独文件,不进 run.json——记录结构契约冻结)
10
+ * upload-ledger.json # 上传台账
11
+ * extracted/<run_id>/<step_id>.json # extract save:true 明细(D-E2,原始数据留在本地)
12
+ *
13
+ * 上传台账语义:去重(已传 run 不重传)、断网重试(未传状态可重试)、
14
+ * 同流程同版本增量。本模块只负责台账的持久化状态管理。
15
+ * 所有 JSON 落盘统一走 A5 原子写(writeJsonAtomic)。
16
+ */
17
+ export type LedgerState = 'pending' | 'uploaded' | 'failed';
18
+ export interface LedgerEntry {
19
+ runId: string;
20
+ flowId: string;
21
+ flowVersion: number;
22
+ state: LedgerState;
23
+ error?: string;
24
+ createdAt: string;
25
+ uploadedAt?: string;
26
+ }
27
+ export interface UploadLedger {
28
+ entries: LedgerEntry[];
29
+ }
30
+ /** 存储目录句柄 */
31
+ export declare class RunStore {
32
+ readonly root: string;
33
+ private readonly ledgerPath;
34
+ constructor(root: string);
35
+ runsDir(): string;
36
+ /** extract save:true 明细目录(D-E2) */
37
+ extractedDir(): string;
38
+ /** DOM 快照目录(D-S2:snapshots/<run_id>.json,不进 run.json) */
39
+ snapshotsDir(): string;
40
+ ledgerPathOf(): string;
41
+ /** 保存执行记录为 runs/<run_id>.json(已存在则覆盖,同 run 幂等);同步入台账 Pending */
42
+ saveRun(rec: RunRecord): string;
43
+ /** 读取执行记录 */
44
+ loadRun(runId: string): RunRecord | undefined;
45
+ /** 列出已落盘的 run_id(按文件名) */
46
+ listRunIds(): string[];
47
+ /** 保存 DOM 快照(覆盖同 run 旧快照,幂等) */
48
+ saveSnapshot(runId: string, snapshot: DomSnapshot): void;
49
+ /** 读取 DOM 快照;无快照或损坏 → undefined(快照失败不阻断 run 落盘) */
50
+ loadSnapshot(runId: string): DomSnapshot | undefined;
51
+ loadLedger(): UploadLedger;
52
+ saveLedger(ledger: UploadLedger): void;
53
+ /** 待上传(Pending/Failed)的 run_id 列表 —— 断网重试的入口 */
54
+ pendingUploads(): string[];
55
+ /** 标记上传结果(去重:同 run_id 更新状态) */
56
+ markUploaded(runId: string, ok: boolean, error?: string): void;
57
+ /** 保存 extract 明细到 extracted/<run_id>/<step_id>.json(原文,永不进 run.json) */
58
+ saveExtracted(runId: string, stepId: string, field: string | undefined, content: string): void;
59
+ }
60
+ /** ExtractSink 的真实落盘实现(M4 接口的 M5 落地) */
61
+ export declare function extractSink(store: RunStore): ExtractSink;
62
+ /** 当前时间(ISO 8601 UTC,秒级) */
63
+ export declare function nowIso(): string;
@@ -0,0 +1,138 @@
1
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { writeFileAtomic, writeJsonAtomic } from './atomic.js';
4
+ /** 存储目录句柄 */
5
+ export class RunStore {
6
+ root;
7
+ ledgerPath;
8
+ constructor(root) {
9
+ this.root = root;
10
+ this.ledgerPath = join(root, 'upload-ledger.json');
11
+ mkdirSync(this.runsDir(), { recursive: true });
12
+ mkdirSync(this.extractedDir(), { recursive: true });
13
+ mkdirSync(this.snapshotsDir(), { recursive: true });
14
+ }
15
+ runsDir() {
16
+ return join(this.root, 'runs');
17
+ }
18
+ /** extract save:true 明细目录(D-E2) */
19
+ extractedDir() {
20
+ return join(this.root, 'extracted');
21
+ }
22
+ /** DOM 快照目录(D-S2:snapshots/<run_id>.json,不进 run.json) */
23
+ snapshotsDir() {
24
+ return join(this.root, 'snapshots');
25
+ }
26
+ ledgerPathOf() {
27
+ return this.ledgerPath;
28
+ }
29
+ /** 保存执行记录为 runs/<run_id>.json(已存在则覆盖,同 run 幂等);同步入台账 Pending */
30
+ saveRun(rec) {
31
+ const path = join(this.runsDir(), `${rec.runId}.json`);
32
+ writeJsonAtomic(path, rec);
33
+ const ledger = this.loadLedger();
34
+ if (!ledger.entries.some((e) => e.runId === rec.runId)) {
35
+ ledger.entries.push({
36
+ runId: rec.runId,
37
+ flowId: rec.flowId,
38
+ flowVersion: rec.flowVersion,
39
+ state: 'pending',
40
+ error: undefined,
41
+ createdAt: rec.startedAt,
42
+ uploadedAt: undefined,
43
+ });
44
+ this.saveLedger(ledger);
45
+ }
46
+ return path;
47
+ }
48
+ /** 读取执行记录 */
49
+ loadRun(runId) {
50
+ const path = join(this.runsDir(), `${runId}.json`);
51
+ try {
52
+ return JSON.parse(readFileSync(path, 'utf8'));
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ }
58
+ /** 列出已落盘的 run_id(按文件名) */
59
+ listRunIds() {
60
+ try {
61
+ const ids = readdirSync(this.runsDir())
62
+ .filter((n) => n.endsWith('.json'))
63
+ .map((n) => n.slice(0, -'.json'.length));
64
+ ids.sort();
65
+ return ids;
66
+ }
67
+ catch {
68
+ return [];
69
+ }
70
+ }
71
+ /* ---- DOM 快照(D-S2:snapshots/<run_id>.json,不进 run.json) ---- */
72
+ /** 保存 DOM 快照(覆盖同 run 旧快照,幂等) */
73
+ saveSnapshot(runId, snapshot) {
74
+ writeJsonAtomic(join(this.snapshotsDir(), `${runId}.json`), snapshot);
75
+ }
76
+ /** 读取 DOM 快照;无快照或损坏 → undefined(快照失败不阻断 run 落盘) */
77
+ loadSnapshot(runId) {
78
+ try {
79
+ return JSON.parse(readFileSync(join(this.snapshotsDir(), `${runId}.json`), 'utf8'));
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ /* ---- 上传台账 ---- */
86
+ loadLedger() {
87
+ try {
88
+ const raw = readFileSync(this.ledgerPath, 'utf8');
89
+ const v = JSON.parse(raw);
90
+ if (Array.isArray(v.entries))
91
+ return v;
92
+ return { entries: [] };
93
+ }
94
+ catch {
95
+ return { entries: [] };
96
+ }
97
+ }
98
+ saveLedger(ledger) {
99
+ writeJsonAtomic(this.ledgerPath, ledger);
100
+ }
101
+ /** 待上传(Pending/Failed)的 run_id 列表 —— 断网重试的入口 */
102
+ pendingUploads() {
103
+ return this.loadLedger()
104
+ .entries.filter((e) => e.state !== 'uploaded')
105
+ .map((e) => e.runId);
106
+ }
107
+ /** 标记上传结果(去重:同 run_id 更新状态) */
108
+ markUploaded(runId, ok, error) {
109
+ const ledger = this.loadLedger();
110
+ const e = ledger.entries.find((x) => x.runId === runId);
111
+ if (e) {
112
+ e.state = ok ? 'uploaded' : 'failed';
113
+ e.error = ok ? undefined : error;
114
+ e.uploadedAt = ok ? nowIso() : undefined;
115
+ this.saveLedger(ledger);
116
+ }
117
+ }
118
+ /* ---- extract save:true 明细落盘(D-E2) ---- */
119
+ /** 保存 extract 明细到 extracted/<run_id>/<step_id>.json(原文,永不进 run.json) */
120
+ saveExtracted(runId, stepId, field, content) {
121
+ const dir = join(this.extractedDir(), runId);
122
+ mkdirSync(dir, { recursive: true });
123
+ writeJsonAtomic(join(dir, `${stepId}.json`), { runId, stepId, field, savedAt: nowIso(), content });
124
+ }
125
+ }
126
+ /** ExtractSink 的真实落盘实现(M4 接口的 M5 落地) */
127
+ export function extractSink(store) {
128
+ return {
129
+ saveExtracted(runId, stepId, field, content) {
130
+ store.saveExtracted(runId, stepId, field, content);
131
+ },
132
+ };
133
+ }
134
+ /** 当前时间(ISO 8601 UTC,秒级) */
135
+ export function nowIso() {
136
+ return new Date().toISOString();
137
+ }
138
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Flow } from './schema.js';
2
+ export type { Flow } from './schema.js';
3
+ /**
4
+ * 解析与语义校验(移植自 Rust `engine/src/lib.rs` 的 `parse_flow` / `validate`)。
5
+ *
6
+ * 语义校验项(与 Rust 版逐条对应,错误文案一字不差):
7
+ * - flow.id 非空 → `flow.id 不能为空`
8
+ * - 步骤 id 非空 → `flow {id} 存在空步骤 id`
9
+ * - 步骤 id 唯一 → `flow {id} 步骤 id 重复: {sid}`
10
+ * - loop 步骤必须含 params.steps → `loop 步骤 {sid} 缺少子步骤 (params.steps)`
11
+ *
12
+ * D-R6 扩展(2026-08-25 定案,Rust 版无此字段):inputs 参数声明校验
13
+ * - 参数名非空 → `inputs 参数名为空`
14
+ * - 参数名流程内唯一 → `inputs 参数名重复: {name}`
15
+ *
16
+ * 错误文案是执行记录 / 观察包与测试断言的对外契约,不得漂移。
17
+ */
18
+ /** 引擎流程定义错误(对齐 Rust `FlowError` 的两档文案前缀) */
19
+ export declare class FlowError extends Error {
20
+ readonly kind: 'parse' | 'invalid';
21
+ readonly name: string;
22
+ constructor(kind: 'parse' | 'invalid', message: string);
23
+ }
24
+ /** 解析失败:`流程定义解析失败: {detail}` */
25
+ export declare function flowParseError(detail: string): FlowError;
26
+ /** 语义校验失败:`流程定义不合法: {detail}` */
27
+ export declare function flowInvalidError(detail: string): FlowError;
28
+ /**
29
+ * 语义校验(等价 Rust `validate`)。返回错误对象,合法返回 null。
30
+ * 错误文案与 Rust 版逐字一致(测试断言依赖原文案)。
31
+ */
32
+ export declare function validate(flow: Flow): FlowError | null;
33
+ /**
34
+ * 完整入口:反序列化 + 语义校验(等价 Rust `parse_flow`)。
35
+ * 解析失败(JSON 语法 / 结构不符)抛 FlowError(kind=parse);校验失败抛 FlowError(kind=invalid)。
36
+ */
37
+ export declare function parseFlow(json: string): Flow;
@@ -0,0 +1,108 @@
1
+ import { parseFlowJson } from './schema.js';
2
+ /**
3
+ * 解析与语义校验(移植自 Rust `engine/src/lib.rs` 的 `parse_flow` / `validate`)。
4
+ *
5
+ * 语义校验项(与 Rust 版逐条对应,错误文案一字不差):
6
+ * - flow.id 非空 → `flow.id 不能为空`
7
+ * - 步骤 id 非空 → `flow {id} 存在空步骤 id`
8
+ * - 步骤 id 唯一 → `flow {id} 步骤 id 重复: {sid}`
9
+ * - loop 步骤必须含 params.steps → `loop 步骤 {sid} 缺少子步骤 (params.steps)`
10
+ *
11
+ * D-R6 扩展(2026-08-25 定案,Rust 版无此字段):inputs 参数声明校验
12
+ * - 参数名非空 → `inputs 参数名为空`
13
+ * - 参数名流程内唯一 → `inputs 参数名重复: {name}`
14
+ *
15
+ * 错误文案是执行记录 / 观察包与测试断言的对外契约,不得漂移。
16
+ */
17
+ /** 引擎流程定义错误(对齐 Rust `FlowError` 的两档文案前缀) */
18
+ export class FlowError extends Error {
19
+ kind;
20
+ name;
21
+ constructor(kind, message) {
22
+ super(message);
23
+ this.kind = kind;
24
+ this.name = kind === 'parse' ? 'FlowParseError' : 'FlowInvalidError';
25
+ }
26
+ }
27
+ /** 解析失败:`流程定义解析失败: {detail}` */
28
+ export function flowParseError(detail) {
29
+ return new FlowError('parse', `流程定义解析失败: ${detail}`);
30
+ }
31
+ /** 语义校验失败:`流程定义不合法: {detail}` */
32
+ export function flowInvalidError(detail) {
33
+ return new FlowError('invalid', `流程定义不合法: ${detail}`);
34
+ }
35
+ /**
36
+ * 语义校验(等价 Rust `validate`)。返回错误对象,合法返回 null。
37
+ * 错误文案与 Rust 版逐字一致(测试断言依赖原文案)。
38
+ */
39
+ export function validate(flow) {
40
+ if (flow.id.length === 0) {
41
+ return flowInvalidError('flow.id 不能为空');
42
+ }
43
+ // D-R6:inputs 参数声明校验(name 非空、流程内唯一)
44
+ const seenInputs = new Set();
45
+ for (const input of flow.inputs ?? []) {
46
+ if (input.name.length === 0) {
47
+ return flowInvalidError('inputs 参数名为空');
48
+ }
49
+ if (seenInputs.has(input.name)) {
50
+ return flowInvalidError(`inputs 参数名重复: ${input.name}`);
51
+ }
52
+ seenInputs.add(input.name);
53
+ }
54
+ const seen = new Set();
55
+ for (const s of flow.steps) {
56
+ if (s.id.length === 0) {
57
+ return flowInvalidError(`flow ${flow.id} 存在空步骤 id`);
58
+ }
59
+ if (seen.has(s.id)) {
60
+ return flowInvalidError(`flow ${flow.id} 步骤 id 重复: ${s.id}`);
61
+ }
62
+ seen.add(s.id);
63
+ if (s.type === 'loop') {
64
+ if (!('steps' in (s.params ?? {}))) {
65
+ return flowInvalidError(`loop 步骤 ${s.id} 缺少子步骤 (params.steps)`);
66
+ }
67
+ // 嵌套 loop 拒绝(EXECUTION-SPEC D-L5:v1 仅支持一层 loop)
68
+ const subs = s.params.steps;
69
+ if (Array.isArray(subs) && subs.some((sub) => isStepOfType(sub, 'loop'))) {
70
+ return flowInvalidError('嵌套 loop 暂不支持');
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ /** 判断未知值是否为指定类型的步骤(validate 层轻量检查,不做完整结构解析) */
77
+ function isStepOfType(v, type) {
78
+ return !!v && typeof v === 'object' && v.type === type;
79
+ }
80
+ /**
81
+ * 完整入口:反序列化 + 语义校验(等价 Rust `parse_flow`)。
82
+ * 解析失败(JSON 语法 / 结构不符)抛 FlowError(kind=parse);校验失败抛 FlowError(kind=invalid)。
83
+ */
84
+ export function parseFlow(json) {
85
+ let raw;
86
+ try {
87
+ raw = JSON.parse(json);
88
+ }
89
+ catch (e) {
90
+ const msg = e instanceof Error ? e.message : String(e);
91
+ throw flowParseError(msg);
92
+ }
93
+ let flow;
94
+ try {
95
+ flow = parseFlowJson(raw);
96
+ }
97
+ catch (e) {
98
+ // 结构不符(如 {"id":123}、steps 非数组)→ zod 抛 ZodError;
99
+ // 对齐 Rust serde 反序列化失败统一为 FlowError::Parse 的语义
100
+ const msg = e instanceof Error ? e.message : String(e);
101
+ throw flowParseError(msg);
102
+ }
103
+ const err = validate(flow);
104
+ if (err)
105
+ throw err;
106
+ return flow;
107
+ }
108
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@qing3a/flow-rpa-engine",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "flow-rpa 执行引擎(原 engine crate):解释执行流程定义(schema 驱动),拟人化操作浏览器",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./src/*.js": {
17
+ "types": "./dist/*.d.ts",
18
+ "default": "./dist/*.js"
19
+ },
20
+ "./test/*.js": "./test/*.js",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": ["dist"],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "test": "vitest run --root ../.. --project engine",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "zod": "^4.4.3"
31
+ }
32
+ }