@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.
- package/dist/atomic.d.ts +9 -0
- package/dist/atomic.js +32 -0
- package/dist/atomic.js.map +1 -0
- package/dist/bench.d.ts +57 -0
- package/dist/bench.js +112 -0
- package/dist/bench.js.map +1 -0
- package/dist/cdp/client.d.ts +70 -0
- package/dist/cdp/client.js +272 -0
- package/dist/cdp/client.js.map +1 -0
- package/dist/cdp/page.d.ts +93 -0
- package/dist/cdp/page.js +411 -0
- package/dist/cdp/page.js.map +1 -0
- package/dist/executor.d.ts +122 -0
- package/dist/executor.js +399 -0
- package/dist/executor.js.map +1 -0
- package/dist/export.d.ts +128 -0
- package/dist/export.js +234 -0
- package/dist/export.js.map +1 -0
- package/dist/gate.d.ts +56 -0
- package/dist/gate.js +144 -0
- package/dist/gate.js.map +1 -0
- package/dist/hello.d.ts +5 -0
- package/dist/hello.js +13 -0
- package/dist/hello.js.map +1 -0
- package/dist/human.d.ts +162 -0
- package/dist/human.js +506 -0
- package/dist/human.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/learning.d.ts +82 -0
- package/dist/learning.js +240 -0
- package/dist/learning.js.map +1 -0
- package/dist/mask.d.ts +32 -0
- package/dist/mask.js +70 -0
- package/dist/mask.js.map +1 -0
- package/dist/platform.d.ts +66 -0
- package/dist/platform.js +215 -0
- package/dist/platform.js.map +1 -0
- package/dist/record.d.ts +48 -0
- package/dist/record.js +42 -0
- package/dist/record.js.map +1 -0
- package/dist/schema.d.ts +130 -0
- package/dist/schema.js +93 -0
- package/dist/schema.js.map +1 -0
- package/dist/simprofile.d.ts +94 -0
- package/dist/simprofile.js +247 -0
- package/dist/simprofile.js.map +1 -0
- package/dist/storage.d.ts +63 -0
- package/dist/storage.js +138 -0
- package/dist/storage.js.map +1 -0
- package/dist/validate.d.ts +37 -0
- package/dist/validate.js +108 -0
- package/dist/validate.js.map +1 -0
- package/package.json +32 -0
package/dist/export.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { writeJsonAtomic } from './atomic.js';
|
|
4
|
+
import { maskKeywordInText, maskKeywords, maskInputValue, maskUrl, PLACEHOLDER_COMPANY, PLACEHOLDER_KEYWORD, PLACEHOLDER_PERSON, PLACEHOLDER_JOB, } from './mask.js';
|
|
5
|
+
import { nowIso, RunStore } from './storage.js';
|
|
6
|
+
/**
|
|
7
|
+
* 脱敏导出 + 观察包 + 本地模拟平台(移植自 Rust `export.rs` + A2 失败类型学 + D-N 编号元素)。
|
|
8
|
+
*
|
|
9
|
+
* 双通道(ARCHITECTURE #6):
|
|
10
|
+
* - **统计通道**(对外默认契约):聚合指标(成功率/选择器命中率/关键词精准率),
|
|
11
|
+
* 绝无单条明细;最小样本门槛(<5 不上传);标识符清洗(<company>/<keyword>/<person>/<job>)。
|
|
12
|
+
* - **观察通道**(开发期默认全开):run.json + DOM 快照 + manifest。
|
|
13
|
+
*
|
|
14
|
+
* A2 失败类型学(EXECUTION-SPEC 第五节):公共失败分类 7 类 ——
|
|
15
|
+
* redirect / verify / selector_miss / timeout / network / parse / unknown。
|
|
16
|
+
* `classifyError` 是统计通道 failure_modes 与 L2 学习信号的公共分类;executor 错误产生处
|
|
17
|
+
* 标注 category(D-F3),本模块的 classifyError 按 D-F2 映射表兜底分类(错误文案级)。
|
|
18
|
+
*
|
|
19
|
+
* D-N 编号元素(EXECUTION-SPEC 第六节):DomSnapshot 含 textSummary + elements(el#N)。
|
|
20
|
+
* 真实浏览器模式 run 后自动 snapshot_dom(D-N1),export_observation 优先用快照(D-N2)。
|
|
21
|
+
*/
|
|
22
|
+
// mask.ts 复用 re-export(MIGRATION-NODE:export.ts 复用 Masker)
|
|
23
|
+
export { maskInputValue, maskKeywordInText, maskKeywords, maskUrl, PLACEHOLDER_COMPANY, PLACEHOLDER_KEYWORD, PLACEHOLDER_PERSON, PLACEHOLDER_JOB };
|
|
24
|
+
/** 最小样本门槛:样本 < 此值不上传(与 L2 成熟度门槛 <5 同构) */
|
|
25
|
+
export const MIN_SAMPLE_THRESHOLD = 5;
|
|
26
|
+
/** 引擎版本(manifest 用;迁移期标注 TS 版) */
|
|
27
|
+
export const ENGINE_VERSION = '0.1.0-ts';
|
|
28
|
+
export const FAILURE_CATEGORIES = [
|
|
29
|
+
'redirect',
|
|
30
|
+
'verify',
|
|
31
|
+
'selector_miss',
|
|
32
|
+
'timeout',
|
|
33
|
+
'network',
|
|
34
|
+
'parse',
|
|
35
|
+
'unknown',
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* 失败分类(D-F2 映射表):错误 → 类别。
|
|
39
|
+
* - 错误对象带 category(executor 标注)→ 直接用(优先)
|
|
40
|
+
* - 否则按错误消息关键词映射:
|
|
41
|
+
* 含「超时」/ timeout → timeout
|
|
42
|
+
* 含「选择器未命中」「点击目标不存在」「等待选择器…超时」(选择器层)→ selector_miss
|
|
43
|
+
* 网络/连接/econn → network
|
|
44
|
+
* 解析/parse/json → parse
|
|
45
|
+
* 登录态/重定向/未登录 → redirect
|
|
46
|
+
* 含「验证失败」/ 页面文本不含 → verify
|
|
47
|
+
* 其他 → unknown
|
|
48
|
+
*/
|
|
49
|
+
export function classifyError(e) {
|
|
50
|
+
// 错误对象带 category(executor 标注,D-F3)
|
|
51
|
+
if (e && typeof e === 'object' && 'category' in e) {
|
|
52
|
+
const c = e.category;
|
|
53
|
+
if (typeof c === 'string' && FAILURE_CATEGORIES.includes(c)) {
|
|
54
|
+
return c;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
58
|
+
const m = msg.toLowerCase();
|
|
59
|
+
if (m.includes('超时') || m.includes('timeout'))
|
|
60
|
+
return 'timeout';
|
|
61
|
+
if (m.includes('选择器未命中') || m.includes('点击目标不存在') || m.includes('等待选择器'))
|
|
62
|
+
return 'selector_miss';
|
|
63
|
+
if (m.includes('econn') || m.includes('网络') || m.includes('连接'))
|
|
64
|
+
return 'network';
|
|
65
|
+
if (m.includes('解析') || m.includes('parse') || m.includes('json'))
|
|
66
|
+
return 'parse';
|
|
67
|
+
if (m.includes('重定向') || m.includes('未登录') || m.includes('登录'))
|
|
68
|
+
return 'redirect';
|
|
69
|
+
if (m.includes('验证失败') || m.includes('页面文本不含') || m.includes('变量缺失'))
|
|
70
|
+
return 'verify';
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 聚合执行记录。records 为空返回 undefined(无样本)。
|
|
75
|
+
* 失败类别用 A2 classifyError(替代 Rust 私有 categorize_error;公共分类)。
|
|
76
|
+
*/
|
|
77
|
+
export function buildStats(records) {
|
|
78
|
+
if (records.length === 0)
|
|
79
|
+
return undefined;
|
|
80
|
+
const sampleCount = records.length;
|
|
81
|
+
const done = records.filter((r) => r.status === 'done').length;
|
|
82
|
+
const successRate = done / sampleCount;
|
|
83
|
+
// 选择器命中率:verify wait_selector 通过的占比
|
|
84
|
+
let selTotal = 0;
|
|
85
|
+
let selHit = 0;
|
|
86
|
+
// 关键词精准率:input 步骤 textContains 通过的占比
|
|
87
|
+
let kwTotal = 0;
|
|
88
|
+
let kwHit = 0;
|
|
89
|
+
let totalDuration = 0;
|
|
90
|
+
const failureMap = new Map();
|
|
91
|
+
for (const rec of records) {
|
|
92
|
+
totalDuration += rec.steps.reduce((a, s) => a + s.durationMs, 0);
|
|
93
|
+
for (const step of rec.steps) {
|
|
94
|
+
if (step.verify) {
|
|
95
|
+
if (step.verify.waitSelector !== undefined) {
|
|
96
|
+
selTotal++;
|
|
97
|
+
if (step.verify.passed)
|
|
98
|
+
selHit++;
|
|
99
|
+
}
|
|
100
|
+
if (step.error) {
|
|
101
|
+
if (step.error.includes('页面文本不含') || step.error.includes('变量缺失')) {
|
|
102
|
+
kwTotal++;
|
|
103
|
+
if (!step.error.includes('不含'))
|
|
104
|
+
kwHit++;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (step.status === 'failed') {
|
|
109
|
+
const err = step.error ?? '';
|
|
110
|
+
const cat = classifyError(err);
|
|
111
|
+
const vtype = step.verify
|
|
112
|
+
? step.verify.waitSelector !== undefined
|
|
113
|
+
? 'wait_selector'
|
|
114
|
+
: 'other'
|
|
115
|
+
: 'none';
|
|
116
|
+
const key = `${step.type ?? ''}|${vtype}|${cat}`;
|
|
117
|
+
failureMap.set(key, (failureMap.get(key) ?? 0) + 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 失败模式按出现次数降序
|
|
122
|
+
const modes = [...failureMap.entries()]
|
|
123
|
+
.map(([key, count]) => {
|
|
124
|
+
const [stepType, verifyType, errorCategory] = key.split('|');
|
|
125
|
+
return { stepType: stepType ?? '', verifyType: verifyType ?? '', errorCategory: errorCategory ?? '', count };
|
|
126
|
+
})
|
|
127
|
+
.sort((a, b) => b.count - a.count);
|
|
128
|
+
return {
|
|
129
|
+
sampleCount,
|
|
130
|
+
successRate,
|
|
131
|
+
selectorHitRate: selTotal > 0 ? selHit / selTotal : 0,
|
|
132
|
+
keywordPrecisionRate: kwTotal > 0 ? kwHit / kwTotal : 0,
|
|
133
|
+
avgDurationMs: Math.floor(totalDuration / sampleCount),
|
|
134
|
+
failureModes: modes,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** 摘要长度(前后各保留字符数) */
|
|
138
|
+
const SUMMARY_CAP = 300;
|
|
139
|
+
/** 生成文本快照摘要(脱敏:截断) */
|
|
140
|
+
export function textSummary(text) {
|
|
141
|
+
const clean = text.split(/\s+/).filter(Boolean).join(' ');
|
|
142
|
+
if (clean.length <= SUMMARY_CAP)
|
|
143
|
+
return clean;
|
|
144
|
+
const half = Math.floor(SUMMARY_CAP / 2);
|
|
145
|
+
const head = clean.slice(0, half);
|
|
146
|
+
const tail = clean.slice(clean.length - half);
|
|
147
|
+
return `${head} … ${tail}`;
|
|
148
|
+
}
|
|
149
|
+
/** 生成编号元素(交互元素按文档序编号;文本截断 ≤40 字符) */
|
|
150
|
+
export function snapshotFromWeb(text, rawElements) {
|
|
151
|
+
const elements = rawElements.map(([tag, t], i) => ({
|
|
152
|
+
index: i + 1,
|
|
153
|
+
tag,
|
|
154
|
+
text: [...t].slice(0, 40).join(''),
|
|
155
|
+
}));
|
|
156
|
+
return { textSummary: textSummary(text), elements, capturedAt: nowIso() };
|
|
157
|
+
}
|
|
158
|
+
/** 从纯文本生成快照(无编号元素;mock 页面用) */
|
|
159
|
+
export function snapshotFromText(text) {
|
|
160
|
+
return snapshotFromWeb(text, []);
|
|
161
|
+
}
|
|
162
|
+
/** 极简哈希(FNV-1a 64 位,与 Rust simple_hash 同算法,UTF-8 字节):用于 source_hash */
|
|
163
|
+
export function simpleHash(s) {
|
|
164
|
+
let h = 0xcbf29ce484222325n;
|
|
165
|
+
for (const b of Buffer.from(s, 'utf8')) {
|
|
166
|
+
h ^= BigInt(b);
|
|
167
|
+
h = (h * 0x100000001b3n) & 0xffffffffffffffffn;
|
|
168
|
+
}
|
|
169
|
+
return h.toString(16).padStart(16, '0');
|
|
170
|
+
}
|
|
171
|
+
/** 从一次执行构建观察报告包(run + 可选 DOM 快照 + 统计通道聚合)。 */
|
|
172
|
+
export function buildObservation(rec, allRuns, domText, domSnapshot) {
|
|
173
|
+
const stats = buildStats(allRuns);
|
|
174
|
+
const snapshot = domSnapshot ?? snapshotFromText(domText ?? '');
|
|
175
|
+
const sourceHash = simpleHash(`${rec.flowId}:${rec.startedAt}`);
|
|
176
|
+
return {
|
|
177
|
+
flowId: rec.flowId,
|
|
178
|
+
flowVersion: rec.flowVersion,
|
|
179
|
+
runId: rec.runId,
|
|
180
|
+
stats: stats && stats.sampleCount >= MIN_SAMPLE_THRESHOLD ? stats : undefined,
|
|
181
|
+
domSnapshot: snapshot,
|
|
182
|
+
run: rec,
|
|
183
|
+
manifest: {
|
|
184
|
+
engineVersion: ENGINE_VERSION,
|
|
185
|
+
flowVersion: rec.flowVersion,
|
|
186
|
+
sourceHash,
|
|
187
|
+
createdAt: nowIso(),
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// ---- 本地模拟平台 ----
|
|
192
|
+
/** 本地模拟平台:把观察报告包落盘到 outbox 目录,并维护上传台账状态机。 */
|
|
193
|
+
export class LocalPlatform {
|
|
194
|
+
root;
|
|
195
|
+
store;
|
|
196
|
+
constructor(root) {
|
|
197
|
+
this.root = root;
|
|
198
|
+
mkdirSync(join(root, 'outbox'), { recursive: true });
|
|
199
|
+
this.store = new RunStore(root);
|
|
200
|
+
}
|
|
201
|
+
/** 观察包目录:observations/<flow_id>_v<version>_<run_id>/ */
|
|
202
|
+
obsDir(flowId, version, runId) {
|
|
203
|
+
return join(this.root, 'outbox', `${flowId}_v${version}_${runId}`);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 组装并「上传」观察包(本地模拟平台):先落盘 run(登记台账 Pending),
|
|
207
|
+
* 再落包 + 标记 Uploaded。allRuns 为流程历史记录(统计通道聚合),
|
|
208
|
+
* domText 为页面文本快照,domSnapshot 为已采集快照(D-N2 优先用)。
|
|
209
|
+
* 返回包目录路径。
|
|
210
|
+
*/
|
|
211
|
+
deliver(rec, allRuns, domText, domSnapshot) {
|
|
212
|
+
this.store.saveRun(rec);
|
|
213
|
+
const bundle = buildObservation(rec, allRuns, domText, domSnapshot);
|
|
214
|
+
const dir = this.obsDir(rec.flowId, rec.flowVersion, rec.runId);
|
|
215
|
+
mkdirSync(dir, { recursive: true });
|
|
216
|
+
// 对齐 md-forge 观察包契约:manifest.json / run.json / dom_snapshot.json 必选
|
|
217
|
+
writeJsonAtomic(join(dir, 'stats.json'), bundle.stats ?? null);
|
|
218
|
+
writeJsonAtomic(join(dir, 'manifest.json'), bundle.manifest);
|
|
219
|
+
writeJsonAtomic(join(dir, 'run.json'), bundle.run);
|
|
220
|
+
writeJsonAtomic(join(dir, 'dom_snapshot.json'), bundle.domSnapshot);
|
|
221
|
+
// 台账:标记已上传(本地模拟平台「上传成功」)
|
|
222
|
+
this.store.markUploaded(rec.runId, true);
|
|
223
|
+
return dir;
|
|
224
|
+
}
|
|
225
|
+
/** 待上传 run_id(台账 Pending/Failed,断网重试入口) */
|
|
226
|
+
pending() {
|
|
227
|
+
return this.store.pendingUploads();
|
|
228
|
+
}
|
|
229
|
+
/** 台账状态查询(诊断用) */
|
|
230
|
+
ledgerState(runId) {
|
|
231
|
+
return this.store.loadLedger().entries.find((e) => e.runId === runId)?.state;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 冷却 + 同站闸门 —— 防封铁律代码化(移植自 Rust `gate.rs`)。
|
|
3
|
+
* 两层:
|
|
4
|
+
* - **任务内冷却(30~60s)**:`CooldownGate` 持久化最近冷却锚点(cool-ledger.json)。
|
|
5
|
+
* key = flow#step#coolKey(D-C2 台账约定);executor 的 wait 步骤读取 coolKey 写入
|
|
6
|
+
* StepResult.cooldownKey,server 层在 run 结束后对每个带 cooldownKey 的步骤记账。
|
|
7
|
+
* - **任务间全局节流(≥10 分钟)**:`SiteGate` 同站两次执行间隔 `GLOBAL_INTER_MIN_SECS`,
|
|
8
|
+
* server 侧强制(acquire),跨站互不阻塞。
|
|
9
|
+
* 本模块不阻塞自身:acquire 失败返回「还需等待秒数」,由调用方决定呈现。
|
|
10
|
+
*/
|
|
11
|
+
/** 任务内冷却区间(秒):流程定义未指定 coolMs 时的默认 */
|
|
12
|
+
export declare const COOLDOWN_MIN_SECS = 30;
|
|
13
|
+
export declare const COOLDOWN_MAX_SECS = 60;
|
|
14
|
+
/** 任务间同站全局节流(秒):≥10 分钟 */
|
|
15
|
+
export declare const GLOBAL_INTER_MIN_SECS = 600;
|
|
16
|
+
/** 冷却台账:key(flow#step#coolKey) → unix 秒 */
|
|
17
|
+
export interface CooldownLedger {
|
|
18
|
+
entries: Record<string, number>;
|
|
19
|
+
}
|
|
20
|
+
/** 任务间同站闸门状态:host → 上次执行结束时间(unix 秒) */
|
|
21
|
+
export interface SiteGateState {
|
|
22
|
+
lastFinish: Record<string, number>;
|
|
23
|
+
}
|
|
24
|
+
/** 从 URL 提取域名(含端口;port 缺省时按 scheme 推断) */
|
|
25
|
+
export declare function hostOf(url: string): string;
|
|
26
|
+
/** 任务内冷却台账(持久化 cool-ledger.json) */
|
|
27
|
+
export declare class CooldownGate {
|
|
28
|
+
private readonly path;
|
|
29
|
+
constructor(root: string);
|
|
30
|
+
private load;
|
|
31
|
+
private save;
|
|
32
|
+
/** 记录一次冷却锚点(wait 步骤执行后调用;key = flow#step#coolKey) */
|
|
33
|
+
record(key: string): void;
|
|
34
|
+
/** 查询最近冷却锚点时间(无记录返回 undefined) */
|
|
35
|
+
lastAt(key: string): number | undefined;
|
|
36
|
+
/** 距上次冷却锚点已过秒数(无记录返回 undefined) */
|
|
37
|
+
elapsedSince(key: string): number | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** 任务间同站闸门(持久化 site-gate.json;server 侧强制) */
|
|
40
|
+
export declare class SiteGate {
|
|
41
|
+
private readonly path;
|
|
42
|
+
constructor(root: string);
|
|
43
|
+
private load;
|
|
44
|
+
private save;
|
|
45
|
+
/**
|
|
46
|
+
* 尝试获取执行许可:距上次同站执行已 ≥ 间隔 → 通过并更新状态;
|
|
47
|
+
* 否则返回 Err(还需等待秒数)。跨站(不同 host)互不影响。
|
|
48
|
+
*/
|
|
49
|
+
acquire(host: string): void | number;
|
|
50
|
+
/** 检查是否可执行(不修改状态;用于展示剩余等待) */
|
|
51
|
+
check(host: string): void | number;
|
|
52
|
+
/** 上次执行时间(诊断) */
|
|
53
|
+
lastFinishAt(host: string): number | undefined;
|
|
54
|
+
}
|
|
55
|
+
/** 等待持续时长(供诊断/统计) */
|
|
56
|
+
export declare function waitDurationLabel(secs: number): string;
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { writeJsonAtomic } from './atomic.js';
|
|
4
|
+
/**
|
|
5
|
+
* 冷却 + 同站闸门 —— 防封铁律代码化(移植自 Rust `gate.rs`)。
|
|
6
|
+
* 两层:
|
|
7
|
+
* - **任务内冷却(30~60s)**:`CooldownGate` 持久化最近冷却锚点(cool-ledger.json)。
|
|
8
|
+
* key = flow#step#coolKey(D-C2 台账约定);executor 的 wait 步骤读取 coolKey 写入
|
|
9
|
+
* StepResult.cooldownKey,server 层在 run 结束后对每个带 cooldownKey 的步骤记账。
|
|
10
|
+
* - **任务间全局节流(≥10 分钟)**:`SiteGate` 同站两次执行间隔 `GLOBAL_INTER_MIN_SECS`,
|
|
11
|
+
* server 侧强制(acquire),跨站互不阻塞。
|
|
12
|
+
* 本模块不阻塞自身:acquire 失败返回「还需等待秒数」,由调用方决定呈现。
|
|
13
|
+
*/
|
|
14
|
+
/** 任务内冷却区间(秒):流程定义未指定 coolMs 时的默认 */
|
|
15
|
+
export const COOLDOWN_MIN_SECS = 30;
|
|
16
|
+
export const COOLDOWN_MAX_SECS = 60;
|
|
17
|
+
/** 任务间同站全局节流(秒):≥10 分钟 */
|
|
18
|
+
export const GLOBAL_INTER_MIN_SECS = 600;
|
|
19
|
+
const unixSecs = () => Math.floor(Date.now() / 1000);
|
|
20
|
+
/** 从 URL 提取域名(含端口;port 缺省时按 scheme 推断) */
|
|
21
|
+
export function hostOf(url) {
|
|
22
|
+
const rest = url.includes('://') ? url.slice(url.indexOf('://') + 3) : url;
|
|
23
|
+
const hostPort = rest.split(/[/?#]/)[0] ?? '';
|
|
24
|
+
// 分离 host 与 port;host 小写
|
|
25
|
+
let host = hostPort;
|
|
26
|
+
let port;
|
|
27
|
+
const idx = hostPort.lastIndexOf(':');
|
|
28
|
+
if (idx !== -1) {
|
|
29
|
+
const p = hostPort.slice(idx + 1);
|
|
30
|
+
if (/^[0-9]+$/.test(p)) {
|
|
31
|
+
host = hostPort.slice(0, idx);
|
|
32
|
+
port = p;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
host = host.toLowerCase();
|
|
36
|
+
if (port === undefined) {
|
|
37
|
+
if (url.startsWith('https://'))
|
|
38
|
+
port = '443';
|
|
39
|
+
else if (url.startsWith('http://'))
|
|
40
|
+
port = '80';
|
|
41
|
+
else
|
|
42
|
+
port = '';
|
|
43
|
+
}
|
|
44
|
+
return port ? `${host}:${port}` : host;
|
|
45
|
+
}
|
|
46
|
+
/** 任务内冷却台账(持久化 cool-ledger.json) */
|
|
47
|
+
export class CooldownGate {
|
|
48
|
+
path;
|
|
49
|
+
constructor(root) {
|
|
50
|
+
this.path = join(root, 'cool-ledger.json');
|
|
51
|
+
mkdirSync(root, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
load() {
|
|
54
|
+
try {
|
|
55
|
+
const v = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
56
|
+
if (v && typeof v === 'object' && typeof v.entries === 'object')
|
|
57
|
+
return v;
|
|
58
|
+
return { entries: {} };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { entries: {} };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
save(ledger) {
|
|
65
|
+
writeJsonAtomic(this.path, ledger);
|
|
66
|
+
}
|
|
67
|
+
/** 记录一次冷却锚点(wait 步骤执行后调用;key = flow#step#coolKey) */
|
|
68
|
+
record(key) {
|
|
69
|
+
const l = this.load();
|
|
70
|
+
l.entries[key] = unixSecs();
|
|
71
|
+
this.save(l);
|
|
72
|
+
}
|
|
73
|
+
/** 查询最近冷却锚点时间(无记录返回 undefined) */
|
|
74
|
+
lastAt(key) {
|
|
75
|
+
return this.load().entries[key];
|
|
76
|
+
}
|
|
77
|
+
/** 距上次冷却锚点已过秒数(无记录返回 undefined) */
|
|
78
|
+
elapsedSince(key) {
|
|
79
|
+
const t = this.lastAt(key);
|
|
80
|
+
return t === undefined ? undefined : Math.max(0, unixSecs() - t);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** 任务间同站闸门(持久化 site-gate.json;server 侧强制) */
|
|
84
|
+
export class SiteGate {
|
|
85
|
+
path;
|
|
86
|
+
constructor(root) {
|
|
87
|
+
this.path = join(root, 'site-gate.json');
|
|
88
|
+
mkdirSync(root, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
load() {
|
|
91
|
+
try {
|
|
92
|
+
const v = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
93
|
+
if (v && typeof v === 'object' && typeof v.lastFinish === 'object')
|
|
94
|
+
return v;
|
|
95
|
+
return { lastFinish: {} };
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return { lastFinish: {} };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
save(state) {
|
|
102
|
+
writeJsonAtomic(this.path, state);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 尝试获取执行许可:距上次同站执行已 ≥ 间隔 → 通过并更新状态;
|
|
106
|
+
* 否则返回 Err(还需等待秒数)。跨站(不同 host)互不影响。
|
|
107
|
+
*/
|
|
108
|
+
acquire(host) {
|
|
109
|
+
const s = this.load();
|
|
110
|
+
const now = unixSecs();
|
|
111
|
+
const last = s.lastFinish[host];
|
|
112
|
+
if (last !== undefined) {
|
|
113
|
+
const elapsed = Math.max(0, now - last);
|
|
114
|
+
if (elapsed < GLOBAL_INTER_MIN_SECS) {
|
|
115
|
+
return GLOBAL_INTER_MIN_SECS - elapsed;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
s.lastFinish[host] = now;
|
|
119
|
+
this.save(s);
|
|
120
|
+
}
|
|
121
|
+
/** 检查是否可执行(不修改状态;用于展示剩余等待) */
|
|
122
|
+
check(host) {
|
|
123
|
+
const s = this.load();
|
|
124
|
+
const now = unixSecs();
|
|
125
|
+
const last = s.lastFinish[host];
|
|
126
|
+
if (last !== undefined) {
|
|
127
|
+
const elapsed = Math.max(0, now - last);
|
|
128
|
+
if (elapsed < GLOBAL_INTER_MIN_SECS) {
|
|
129
|
+
return GLOBAL_INTER_MIN_SECS - elapsed;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** 上次执行时间(诊断) */
|
|
134
|
+
lastFinishAt(host) {
|
|
135
|
+
return this.load().lastFinish[host];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/** 等待持续时长(供诊断/统计) */
|
|
139
|
+
export function waitDurationLabel(secs) {
|
|
140
|
+
if (secs >= 60)
|
|
141
|
+
return `${Math.floor(secs / 60)} 分钟 ${secs % 60} 秒`;
|
|
142
|
+
return `${secs} 秒`;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=gate.js.map
|
package/dist/gate.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../src/gate.ts"],"names":[],"mappings":"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"}
|
package/dist/hello.d.ts
ADDED
package/dist/hello.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
/**
|
|
3
|
+
* M1 骨架占位:验证 tsx 直跑链路(Rust → TypeScript 迁移脚手架)。
|
|
4
|
+
* 正式模块(schema/executor/storage/…)在 M2 起按 MIGRATION-NODE 模块映射落地。
|
|
5
|
+
*/
|
|
6
|
+
export function engineHello() {
|
|
7
|
+
return 'engine hello from flow-rpa (tsx)';
|
|
8
|
+
}
|
|
9
|
+
// 直接运行(pnpm hello)时打印;被测试 import 时无副作用
|
|
10
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
11
|
+
console.log(engineHello());
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=hello.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hello.js","sourceRoot":"","sources":["../src/hello.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,kCAAkC,CAAA;AAC3C,CAAC;AAED,uCAAuC;AACvC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAA;AAC5B,CAAC"}
|
package/dist/human.d.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { SimProfile } from './simprofile.js';
|
|
2
|
+
import { type Rng } from './simprofile.js';
|
|
3
|
+
/**
|
|
4
|
+
* 人类行为模拟层 —— 从 hr-rpa-mcp `src/core/human.mjs` 完整移植(Rust `human.rs` 已 1:1 复刻,
|
|
5
|
+
* TS 按 Rust 语义翻译,三者一致)。
|
|
6
|
+
*
|
|
7
|
+
* 算法(不改写):
|
|
8
|
+
* - mouseMove: steps = clamp(ceil(dist/10), 6, 100) · jitter · smoothstep 缓动 · 每步对数正态延迟
|
|
9
|
+
* - mouseClick: 预移动(>6px) · 悬停 150~400ms · 30% 过冲 2~6px · 按下中位 120ms(σ.45) · 可选双击
|
|
10
|
+
* - mouseWheel: 惯性递减(比例 .7~.85,6~12 段)或均匀(3~6 段)· 段间 20~60ms · ±1px 抖动
|
|
11
|
+
* - mouseDrag: 预移动 → 按下 → 按住拖动 → 释放(各段对数正态节奏)
|
|
12
|
+
* - keyboardInput: WPM 180~280 · 2% 打错重打 · 5% 思考停顿(500~1500ms) · 收尾 200ms
|
|
13
|
+
*
|
|
14
|
+
* 解耦:Human 通过 `InputSink` 接口发送 CDP 事件(不直接依赖 CDP 实现),
|
|
15
|
+
* 拟人算法可用 mock sink 完整单测。
|
|
16
|
+
*/
|
|
17
|
+
/** 坐标点(内部 f64;dispatch 时四舍五入) */
|
|
18
|
+
export interface Point {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
export declare const point: (x: number, y: number) => Point;
|
|
23
|
+
/** 矩形(randomClickIn / randomPointIn 用) */
|
|
24
|
+
export interface Rect {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
w: number;
|
|
28
|
+
h: number;
|
|
29
|
+
}
|
|
30
|
+
/** 拟人操作错误 */
|
|
31
|
+
export declare class HumanError extends Error {
|
|
32
|
+
readonly kind: 'sink' | 'timeout' | 'stopped';
|
|
33
|
+
constructor(kind: 'sink' | 'timeout' | 'stopped', message: string);
|
|
34
|
+
static sink(msg: string): HumanError;
|
|
35
|
+
static timeout(op: string): HumanError;
|
|
36
|
+
static stopped(): HumanError;
|
|
37
|
+
}
|
|
38
|
+
/** CDP 事件发送抽象:真实浏览器用 CDP 客户端实现,测试用 mock 实现 */
|
|
39
|
+
export interface InputSink {
|
|
40
|
+
/** 发送 CDP 命令(如 Input.dispatchMouseEvent) */
|
|
41
|
+
send(method: string, params: Record<string, unknown>): Promise<void>;
|
|
42
|
+
/** 页面求值(randomClickIn 获取元素矩形等) */
|
|
43
|
+
evaluate(expr: string): Promise<unknown>;
|
|
44
|
+
/** 视口尺寸(Page.getLayoutMetrics → visualViewport;失败回退 1440x900) */
|
|
45
|
+
viewport(): Promise<[number, number]>;
|
|
46
|
+
}
|
|
47
|
+
/** 插件: clamp(e,t,s){return Math.max(t,Math.min(s,e))} */
|
|
48
|
+
export declare function clamp<T>(v: T, min: T, max: T): T;
|
|
49
|
+
/** 插件: randBetween(e,t,s=Math.random){return e+s()*(t-e)} */
|
|
50
|
+
export declare function randBetween(a: number, b: number, rng: Rng): number;
|
|
51
|
+
/** 插件: sampleLogNormal —— Box-Muller 对数正态,clamp 8~600ms */
|
|
52
|
+
export declare function sampleLogNormal(median: number, sigma: number, rng: Rng): number;
|
|
53
|
+
/** 插件: easeInOut(e){return e*e*(3-2*e)} */
|
|
54
|
+
export declare function easeInOut(t: number): number;
|
|
55
|
+
export declare function getRandomErrorChar(ch: string, rng: Rng): string;
|
|
56
|
+
export interface MoveOpts {
|
|
57
|
+
from?: Point;
|
|
58
|
+
clamp?: boolean;
|
|
59
|
+
jitter?: number;
|
|
60
|
+
seed?: number;
|
|
61
|
+
timeoutMs?: number;
|
|
62
|
+
/** 停止标志:true 时中止(对应插件 throwIfAborted 的 stopCheck) */
|
|
63
|
+
stop?: {
|
|
64
|
+
stopped: boolean;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export interface ClickOpts {
|
|
68
|
+
clamp?: boolean;
|
|
69
|
+
seed?: number;
|
|
70
|
+
hoverRangeMs?: [number, number];
|
|
71
|
+
overshootProbability?: number;
|
|
72
|
+
overshootPx?: [number, number];
|
|
73
|
+
pressMedianMs?: number;
|
|
74
|
+
doubleClick?: boolean;
|
|
75
|
+
doubleClickProbability?: number;
|
|
76
|
+
keyboard?: string[];
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
stop?: {
|
|
79
|
+
stopped: boolean;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export interface WheelOpts {
|
|
83
|
+
x?: number;
|
|
84
|
+
y?: number;
|
|
85
|
+
inertia?: boolean;
|
|
86
|
+
steps?: number;
|
|
87
|
+
seed?: number;
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
stop?: {
|
|
90
|
+
stopped: boolean;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export interface DragOpts {
|
|
94
|
+
steps?: number;
|
|
95
|
+
jitter?: number;
|
|
96
|
+
pressDelayMs?: [number, number];
|
|
97
|
+
releaseDelayMs?: [number, number];
|
|
98
|
+
clamp?: boolean;
|
|
99
|
+
seed?: number;
|
|
100
|
+
timeoutMs?: number;
|
|
101
|
+
stop?: {
|
|
102
|
+
stopped: boolean;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export interface InputOpts {
|
|
106
|
+
wpmRange?: [number, number];
|
|
107
|
+
errorRate?: number;
|
|
108
|
+
thinkPauseChance?: number;
|
|
109
|
+
thinkPauseRangeMs?: [number, number];
|
|
110
|
+
finalPauseMs?: number;
|
|
111
|
+
seed?: number;
|
|
112
|
+
timeoutMs?: number;
|
|
113
|
+
stop?: {
|
|
114
|
+
stopped: boolean;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** 拟人操作器:持有 InputSink,行为参数来自 SimProfile(机器差异化) */
|
|
118
|
+
export declare class Human<S extends InputSink> {
|
|
119
|
+
private readonly sink;
|
|
120
|
+
private lastPos?;
|
|
121
|
+
private readonly sim;
|
|
122
|
+
constructor(sink: S, sim: SimProfile);
|
|
123
|
+
static withDefaultSim<S extends InputSink>(sink: S): Human<S>;
|
|
124
|
+
/** 插件 assertPageOk 前置校验(forbidden scheme 检查) */
|
|
125
|
+
assertPageOk(): Promise<void>;
|
|
126
|
+
/** 插件 getViewport:Page.getLayoutMetrics.visualViewport,fallback 1440x900 */
|
|
127
|
+
private getViewport;
|
|
128
|
+
/** 插件 U:坐标 clamp 到视口(输出四舍五入) */
|
|
129
|
+
static clampToViewport(p: Point, w: number, h: number): Point;
|
|
130
|
+
private checkStop;
|
|
131
|
+
/** mouseMove 核心实现 */
|
|
132
|
+
private implMove;
|
|
133
|
+
/** 插件 mouseMove(公共入口:可选超时) */
|
|
134
|
+
mouseMove(to: Point, opts?: MoveOpts): Promise<Point>;
|
|
135
|
+
/** mouseClick 核心 */
|
|
136
|
+
private implClick;
|
|
137
|
+
/** 插件 mouseClick(公共入口) */
|
|
138
|
+
mouseClick(at: Point, opts?: ClickOpts): Promise<Point>;
|
|
139
|
+
/** 插件 mouseWheel:惯性递减或均匀,段间 20~60ms,±1px */
|
|
140
|
+
mouseWheel(delta: Point, opts?: WheelOpts): Promise<Point>;
|
|
141
|
+
/** 插件 mx:矩形内按 padding 收缩后随机取点 */
|
|
142
|
+
static randomPointIn(rect: Rect, padding: [number, number, number, number], rng: Rng): Point;
|
|
143
|
+
/** 插件 mouseDrag(content_to_bg_mouse_drag,1:1 复刻) */
|
|
144
|
+
mouseDrag(from: Point, to: Point, opts?: DragOpts): Promise<[Point, Point]>;
|
|
145
|
+
/**
|
|
146
|
+
* 插件 randomClickIn:随机选元素在其可见矩形内随机点后 mouseClick,1~2 次。
|
|
147
|
+
* 需要页面已注入 DOM_SOURCE(window.__rpaDom.getBySels)。
|
|
148
|
+
*/
|
|
149
|
+
randomClickIn(selector: string, maxClicks?: number, intervalMs?: number): Promise<[number, number]>;
|
|
150
|
+
/** 插件 typeCharacter */
|
|
151
|
+
private typeCharacter;
|
|
152
|
+
/** 插件 typeBackspace */
|
|
153
|
+
private typeBackspace;
|
|
154
|
+
/** 按回车(真实 CDP 键盘事件,isTrusted=true) */
|
|
155
|
+
pressEnter(): Promise<void>;
|
|
156
|
+
/** 插件 isThinkingTrigger:标点后思考停顿 */
|
|
157
|
+
static isThinkingTrigger(ch: string): boolean;
|
|
158
|
+
/** 插件 calculateTypingInterval:相邻字符间隔加权,clamp 30~500ms */
|
|
159
|
+
calculateTypingInterval(prev: string, next: string, baseMs: number, rng: Rng): number;
|
|
160
|
+
/** 插件 keyboardInput:WPM 180~280 · 2% 打错重打 · 5% 思考停顿 · 收尾 200ms */
|
|
161
|
+
keyboardInput(text: string, opts?: InputOpts): Promise<number>;
|
|
162
|
+
}
|