@reconcrap/boss-recommend-mcp 2.0.43 → 2.0.45
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/README.md +7 -1
- package/config/screening-config.example.json +11 -1
- package/package.json +1 -2
- package/src/chat-mcp.js +4 -0
- package/src/chat-runtime-config.js +85 -1
- package/src/cli.js +13 -1
- package/src/core/browser/index.js +652 -4
- package/src/core/capture/index.js +118 -14
- package/src/core/infinite-list/index.js +122 -8
- package/src/domains/chat/run-service.js +121 -5
- package/src/domains/recommend/run-service.js +200 -95
- package/src/domains/recruit/run-service.js +108 -4
- package/src/index.js +58 -0
- package/src/recommend-mcp.js +22 -18
- package/src/recruit-mcp.js +44 -0
- package/scripts/live-recommend-recovery-smoke.js +0 -305
|
@@ -40,6 +40,212 @@ const BOSS_LOGIN_DOM_SELECTORS = [
|
|
|
40
40
|
"input[placeholder*='手机号']",
|
|
41
41
|
"input[placeholder*='验证码']"
|
|
42
42
|
];
|
|
43
|
+
const HUMAN_INTERACTION_CONFIG = new WeakMap();
|
|
44
|
+
const DEFAULT_HUMAN_BEHAVIOR_PROFILE = "paced_with_rests";
|
|
45
|
+
const HUMAN_BEHAVIOR_PROFILES = Object.freeze({
|
|
46
|
+
baseline: Object.freeze({
|
|
47
|
+
enabled: false,
|
|
48
|
+
clickMovement: false,
|
|
49
|
+
textEntry: false,
|
|
50
|
+
listScrollJitter: false,
|
|
51
|
+
shortRest: false,
|
|
52
|
+
batchRest: false,
|
|
53
|
+
actionCooldown: false
|
|
54
|
+
}),
|
|
55
|
+
paced: Object.freeze({
|
|
56
|
+
enabled: true,
|
|
57
|
+
clickMovement: true,
|
|
58
|
+
textEntry: true,
|
|
59
|
+
listScrollJitter: true,
|
|
60
|
+
shortRest: false,
|
|
61
|
+
batchRest: false,
|
|
62
|
+
actionCooldown: true
|
|
63
|
+
}),
|
|
64
|
+
paced_with_rests: Object.freeze({
|
|
65
|
+
enabled: true,
|
|
66
|
+
clickMovement: true,
|
|
67
|
+
textEntry: true,
|
|
68
|
+
listScrollJitter: true,
|
|
69
|
+
shortRest: true,
|
|
70
|
+
batchRest: true,
|
|
71
|
+
actionCooldown: true
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
const HUMAN_BEHAVIOR_PROFILE_ALIASES = Object.freeze({
|
|
75
|
+
off: "baseline",
|
|
76
|
+
disabled: "baseline",
|
|
77
|
+
deterministic: "baseline",
|
|
78
|
+
safe: "paced",
|
|
79
|
+
safe_pacing: "paced",
|
|
80
|
+
paced_with_rest: "paced_with_rests",
|
|
81
|
+
rests: "paced_with_rests",
|
|
82
|
+
rest: "paced_with_rests"
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function clampNumber(value, min, max) {
|
|
86
|
+
const number = Number(value);
|
|
87
|
+
if (!Number.isFinite(number)) return min;
|
|
88
|
+
return Math.min(max, Math.max(min, number));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function randomBetween(random, min, max) {
|
|
92
|
+
const lower = Number(min) || 0;
|
|
93
|
+
const upper = Number(max) || lower;
|
|
94
|
+
if (upper <= lower) return lower;
|
|
95
|
+
return lower + random() * (upper - lower);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function randomIntegerBetween(random, min, max) {
|
|
99
|
+
return Math.floor(randomBetween(random, min, max + 1));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizePoint(point) {
|
|
103
|
+
const x = Number(point?.x);
|
|
104
|
+
const y = Number(point?.y);
|
|
105
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
106
|
+
return { x, y };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeRandom(random) {
|
|
110
|
+
return typeof random === "function" ? random : Math.random;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getHumanInteractionConfig(client) {
|
|
114
|
+
return HUMAN_INTERACTION_CONFIG.get(client) || null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function normalizeBooleanOption(raw, fallback = null) {
|
|
118
|
+
if (typeof raw === "boolean") return raw;
|
|
119
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw !== 0;
|
|
120
|
+
const normalized = String(raw ?? "").trim().toLowerCase();
|
|
121
|
+
if (!normalized) return fallback;
|
|
122
|
+
if (["true", "1", "yes", "y", "on", "enabled"].includes(normalized)) return true;
|
|
123
|
+
if (["false", "0", "no", "n", "off", "disabled"].includes(normalized)) return false;
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function readFirstOption(source, keys = []) {
|
|
128
|
+
if (!source || typeof source !== "object") return undefined;
|
|
129
|
+
for (const key of keys) {
|
|
130
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeFeatureBoolean(raw, fallback) {
|
|
136
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
137
|
+
return normalizeBooleanOption(readFirstOption(raw, ["enabled", "enable"]), fallback);
|
|
138
|
+
}
|
|
139
|
+
return normalizeBooleanOption(raw, fallback);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function normalizeHumanBehaviorProfile(raw, fallback = "baseline") {
|
|
143
|
+
const normalized = String(raw || "").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
144
|
+
const profile = HUMAN_BEHAVIOR_PROFILE_ALIASES[normalized] || normalized;
|
|
145
|
+
return Object.prototype.hasOwnProperty.call(HUMAN_BEHAVIOR_PROFILES, profile)
|
|
146
|
+
? profile
|
|
147
|
+
: fallback;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function normalizeHumanBehaviorOptions(raw = null, {
|
|
151
|
+
legacyEnabled = false,
|
|
152
|
+
safePacing = null,
|
|
153
|
+
batchRestEnabled = null
|
|
154
|
+
} = {}) {
|
|
155
|
+
const safePacingFlag = normalizeBooleanOption(safePacing, null);
|
|
156
|
+
const batchRestFlag = normalizeBooleanOption(batchRestEnabled, null);
|
|
157
|
+
let source = "default";
|
|
158
|
+
let rawObject = {};
|
|
159
|
+
if (typeof raw === "boolean") {
|
|
160
|
+
rawObject = { enabled: raw };
|
|
161
|
+
source = "boolean";
|
|
162
|
+
} else if (typeof raw === "string") {
|
|
163
|
+
rawObject = { profile: raw };
|
|
164
|
+
source = "profile";
|
|
165
|
+
} else if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
166
|
+
rawObject = raw;
|
|
167
|
+
source = "object";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const explicitProfile = readFirstOption(rawObject, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
171
|
+
const enabledRaw = readFirstOption(rawObject, ["enabled", "enable", "human_behavior_enabled"]);
|
|
172
|
+
const explicitEnabled = normalizeBooleanOption(enabledRaw, null);
|
|
173
|
+
const inferredProfile = (raw === true || explicitEnabled === true) && legacyEnabled !== true && batchRestFlag !== true
|
|
174
|
+
? "paced"
|
|
175
|
+
: legacyEnabled === true || batchRestFlag === true
|
|
176
|
+
? "paced_with_rests"
|
|
177
|
+
: safePacingFlag === true
|
|
178
|
+
? "paced"
|
|
179
|
+
: DEFAULT_HUMAN_BEHAVIOR_PROFILE;
|
|
180
|
+
const profile = normalizeHumanBehaviorProfile(explicitProfile, inferredProfile);
|
|
181
|
+
const profileDefaults = {
|
|
182
|
+
...HUMAN_BEHAVIOR_PROFILES[profile]
|
|
183
|
+
};
|
|
184
|
+
if (legacyEnabled === true && !explicitProfile) {
|
|
185
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced_with_rests);
|
|
186
|
+
} else if (safePacingFlag === true && !explicitProfile) {
|
|
187
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced);
|
|
188
|
+
}
|
|
189
|
+
if (batchRestFlag === true && !explicitProfile) {
|
|
190
|
+
Object.assign(profileDefaults, HUMAN_BEHAVIOR_PROFILES.paced_with_rests);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const hasExplicitEnabled = enabledRaw !== undefined;
|
|
194
|
+
if (hasExplicitEnabled) {
|
|
195
|
+
profileDefaults.enabled = normalizeBooleanOption(enabledRaw, profileDefaults.enabled);
|
|
196
|
+
}
|
|
197
|
+
if (!hasExplicitEnabled && (safePacingFlag === false || batchRestFlag === false) && !explicitProfile && legacyEnabled !== true) {
|
|
198
|
+
profileDefaults.enabled = false;
|
|
199
|
+
}
|
|
200
|
+
if (!hasExplicitEnabled && (safePacingFlag === true || batchRestFlag === true || legacyEnabled === true)) {
|
|
201
|
+
profileDefaults.enabled = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const enabled = profileDefaults.enabled === true;
|
|
205
|
+
const clickMovement = normalizeFeatureBoolean(
|
|
206
|
+
readFirstOption(rawObject, ["clickMovement", "click_movement", "click_movement_enabled"]),
|
|
207
|
+
profileDefaults.clickMovement
|
|
208
|
+
);
|
|
209
|
+
const textEntry = normalizeFeatureBoolean(
|
|
210
|
+
readFirstOption(rawObject, ["textEntry", "text_entry", "text_entry_enabled"]),
|
|
211
|
+
profileDefaults.textEntry
|
|
212
|
+
);
|
|
213
|
+
const listScrollJitter = normalizeFeatureBoolean(
|
|
214
|
+
readFirstOption(rawObject, ["listScrollJitter", "list_scroll_jitter", "scrollJitter", "scroll_jitter"]),
|
|
215
|
+
profileDefaults.listScrollJitter
|
|
216
|
+
);
|
|
217
|
+
const actionCooldown = normalizeFeatureBoolean(
|
|
218
|
+
readFirstOption(rawObject, ["actionCooldown", "action_cooldown", "readPause", "read_pause"]),
|
|
219
|
+
profileDefaults.actionCooldown
|
|
220
|
+
);
|
|
221
|
+
let shortRest = normalizeFeatureBoolean(
|
|
222
|
+
readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]),
|
|
223
|
+
profileDefaults.shortRest
|
|
224
|
+
);
|
|
225
|
+
let batchRest = normalizeFeatureBoolean(
|
|
226
|
+
readFirstOption(rawObject, ["batchRest", "batch_rest", "batchRestEnabled", "batch_rest_enabled"]),
|
|
227
|
+
profileDefaults.batchRest
|
|
228
|
+
);
|
|
229
|
+
if (batchRestFlag !== null) {
|
|
230
|
+
batchRest = batchRestFlag;
|
|
231
|
+
if (batchRestFlag === true && readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]) === undefined) {
|
|
232
|
+
shortRest = true;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
enabled,
|
|
238
|
+
profile,
|
|
239
|
+
source,
|
|
240
|
+
clickMovement: enabled && clickMovement === true,
|
|
241
|
+
textEntry: enabled && textEntry === true,
|
|
242
|
+
listScrollJitter: enabled && listScrollJitter === true,
|
|
243
|
+
shortRest: enabled && shortRest === true,
|
|
244
|
+
batchRest: enabled && batchRest === true,
|
|
245
|
+
actionCooldown: enabled && actionCooldown === true,
|
|
246
|
+
restEnabled: enabled && (shortRest === true || batchRest === true)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
43
249
|
|
|
44
250
|
function nowIso() {
|
|
45
251
|
return new Date().toISOString();
|
|
@@ -76,6 +282,200 @@ export function assertNoForbiddenCdpCalls(methodLog = []) {
|
|
|
76
282
|
}
|
|
77
283
|
}
|
|
78
284
|
|
|
285
|
+
export function humanDelay(baseMs, varianceMs, {
|
|
286
|
+
minMs = 100,
|
|
287
|
+
maxMs = 60000,
|
|
288
|
+
random = Math.random
|
|
289
|
+
} = {}) {
|
|
290
|
+
const nextRandom = normalizeRandom(random);
|
|
291
|
+
const base = Math.max(0, Number(baseMs) || 0);
|
|
292
|
+
const variance = Math.max(0, Number(varianceMs) || 0);
|
|
293
|
+
const lower = Math.max(0, Number(minMs) || 0);
|
|
294
|
+
const upper = Math.max(lower, Number(maxMs) || lower);
|
|
295
|
+
if (variance <= 0) return Math.round(clampNumber(base, lower, upper));
|
|
296
|
+
const u1 = Math.max(Number.EPSILON, Math.min(1 - Number.EPSILON, nextRandom()));
|
|
297
|
+
const u2 = Math.max(Number.EPSILON, Math.min(1 - Number.EPSILON, nextRandom()));
|
|
298
|
+
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
299
|
+
return Math.round(clampNumber(base + z * variance, lower, upper));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function generateBezierPath(start, end, {
|
|
303
|
+
steps = 18,
|
|
304
|
+
random = Math.random,
|
|
305
|
+
controlJitterX = 100,
|
|
306
|
+
controlJitterY = 60
|
|
307
|
+
} = {}) {
|
|
308
|
+
const startPoint = normalizePoint(start);
|
|
309
|
+
const endPoint = normalizePoint(end);
|
|
310
|
+
if (!startPoint || !endPoint) {
|
|
311
|
+
throw new Error("generateBezierPath requires finite start and end points");
|
|
312
|
+
}
|
|
313
|
+
const nextRandom = normalizeRandom(random);
|
|
314
|
+
const safeSteps = Math.max(1, Math.floor(Number(steps) || 18));
|
|
315
|
+
const midX = (startPoint.x + endPoint.x) / 2 + (nextRandom() - 0.5) * Math.max(0, Number(controlJitterX) || 0);
|
|
316
|
+
const midY = (startPoint.y + endPoint.y) / 2 + (nextRandom() - 0.5) * Math.max(0, Number(controlJitterY) || 0);
|
|
317
|
+
const path = [];
|
|
318
|
+
for (let index = 0; index <= safeSteps; index += 1) {
|
|
319
|
+
const t = index / safeSteps;
|
|
320
|
+
const inverse = 1 - t;
|
|
321
|
+
path.push({
|
|
322
|
+
x: inverse * inverse * startPoint.x + 2 * inverse * t * midX + t * t * endPoint.x,
|
|
323
|
+
y: inverse * inverse * startPoint.y + 2 * inverse * t * midY + t * t * endPoint.y
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return path;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function configureHumanInteraction(client, {
|
|
330
|
+
enabled = false,
|
|
331
|
+
clickMovementEnabled = null,
|
|
332
|
+
textEntryEnabled = null,
|
|
333
|
+
safeClickPointEnabled = null,
|
|
334
|
+
actionCooldownEnabled = null,
|
|
335
|
+
random = Math.random,
|
|
336
|
+
sleepFn = null,
|
|
337
|
+
moveSteps = 18,
|
|
338
|
+
moveJitterPx = 3,
|
|
339
|
+
hoverJitterPx = 5,
|
|
340
|
+
moveDelayMinMs = 5,
|
|
341
|
+
moveDelayMaxMs = 23,
|
|
342
|
+
hoverDelayMinMs = 10,
|
|
343
|
+
hoverDelayMaxMs = 30,
|
|
344
|
+
prePressBaseMs = 260,
|
|
345
|
+
prePressVarianceMs = 80,
|
|
346
|
+
holdVarianceMs = 30,
|
|
347
|
+
safeClickMinWidth = 44,
|
|
348
|
+
safeClickMinHeight = 28,
|
|
349
|
+
safeClickInsetRatio = 0.22,
|
|
350
|
+
safeClickMinInsetPx = 4,
|
|
351
|
+
safeClickMaxInsetPx = 18,
|
|
352
|
+
textChunkMinLength = 1,
|
|
353
|
+
textChunkMaxLength = 5,
|
|
354
|
+
textChunkDelayBaseMs = 55,
|
|
355
|
+
textChunkDelayVarianceMs = 30
|
|
356
|
+
} = {}) {
|
|
357
|
+
const previous = getHumanInteractionConfig(client);
|
|
358
|
+
const normalizedEnabled = enabled === true;
|
|
359
|
+
HUMAN_INTERACTION_CONFIG.set(client, {
|
|
360
|
+
enabled: normalizedEnabled,
|
|
361
|
+
clickMovementEnabled: normalizedEnabled && clickMovementEnabled !== false,
|
|
362
|
+
textEntryEnabled: normalizedEnabled && textEntryEnabled !== false,
|
|
363
|
+
safeClickPointEnabled: normalizedEnabled && safeClickPointEnabled !== false,
|
|
364
|
+
actionCooldownEnabled: normalizedEnabled && actionCooldownEnabled !== false,
|
|
365
|
+
random: normalizeRandom(random),
|
|
366
|
+
sleepFn: typeof sleepFn === "function" ? sleepFn : sleep,
|
|
367
|
+
moveSteps: Math.max(1, Math.floor(Number(moveSteps) || 18)),
|
|
368
|
+
moveJitterPx: Math.max(0, Number(moveJitterPx) || 0),
|
|
369
|
+
hoverJitterPx: Math.max(0, Number(hoverJitterPx) || 0),
|
|
370
|
+
moveDelayMinMs: Math.max(0, Number(moveDelayMinMs) || 0),
|
|
371
|
+
moveDelayMaxMs: Math.max(0, Number(moveDelayMaxMs) || 0),
|
|
372
|
+
hoverDelayMinMs: Math.max(0, Number(hoverDelayMinMs) || 0),
|
|
373
|
+
hoverDelayMaxMs: Math.max(0, Number(hoverDelayMaxMs) || 0),
|
|
374
|
+
prePressBaseMs: Math.max(0, Number(prePressBaseMs) || 0),
|
|
375
|
+
prePressVarianceMs: Math.max(0, Number(prePressVarianceMs) || 0),
|
|
376
|
+
holdVarianceMs: Math.max(0, Number(holdVarianceMs) || 0),
|
|
377
|
+
safeClickMinWidth: Math.max(1, Number(safeClickMinWidth) || 44),
|
|
378
|
+
safeClickMinHeight: Math.max(1, Number(safeClickMinHeight) || 28),
|
|
379
|
+
safeClickInsetRatio: clampNumber(safeClickInsetRatio, 0.05, 0.45),
|
|
380
|
+
safeClickMinInsetPx: Math.max(0, Number(safeClickMinInsetPx) || 0),
|
|
381
|
+
safeClickMaxInsetPx: Math.max(0, Number(safeClickMaxInsetPx) || 0),
|
|
382
|
+
textChunkMinLength: Math.max(1, Math.floor(Number(textChunkMinLength) || 1)),
|
|
383
|
+
textChunkMaxLength: Math.max(1, Math.floor(Number(textChunkMaxLength) || 5)),
|
|
384
|
+
textChunkDelayBaseMs: Math.max(0, Number(textChunkDelayBaseMs) || 0),
|
|
385
|
+
textChunkDelayVarianceMs: Math.max(0, Number(textChunkDelayVarianceMs) || 0),
|
|
386
|
+
lastMousePoint: previous?.lastMousePoint || null
|
|
387
|
+
});
|
|
388
|
+
return () => {
|
|
389
|
+
if (previous) {
|
|
390
|
+
HUMAN_INTERACTION_CONFIG.set(client, previous);
|
|
391
|
+
} else {
|
|
392
|
+
HUMAN_INTERACTION_CONFIG.delete(client);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function createHumanRestController({
|
|
398
|
+
enabled = false,
|
|
399
|
+
shortRestEnabled = true,
|
|
400
|
+
batchRestEnabled = true,
|
|
401
|
+
random = Math.random,
|
|
402
|
+
shortRestProbability = 0.08,
|
|
403
|
+
shortRestMinMs = 3000,
|
|
404
|
+
shortRestMaxMs = 7000,
|
|
405
|
+
batchThresholdBase = 25,
|
|
406
|
+
batchThresholdJitter = 8,
|
|
407
|
+
batchRestMinMs = 15000,
|
|
408
|
+
batchRestMaxMs = 30000
|
|
409
|
+
} = {}) {
|
|
410
|
+
const nextRandom = normalizeRandom(random);
|
|
411
|
+
const state = {
|
|
412
|
+
enabled: enabled === true,
|
|
413
|
+
short_rest_enabled: enabled === true && shortRestEnabled !== false,
|
|
414
|
+
batch_rest_enabled: enabled === true && batchRestEnabled !== false,
|
|
415
|
+
rest_counter: 0,
|
|
416
|
+
rest_threshold: Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1))),
|
|
417
|
+
rest_count: 0,
|
|
418
|
+
total_rest_ms: 0
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
function resetThreshold() {
|
|
422
|
+
state.rest_threshold = Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1)));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function takeBreakIfNeeded({ sleepFn = sleep } = {}) {
|
|
426
|
+
if (!state.enabled) {
|
|
427
|
+
return {
|
|
428
|
+
enabled: false,
|
|
429
|
+
rested: false,
|
|
430
|
+
rest_counter: state.rest_counter,
|
|
431
|
+
rest_threshold: state.rest_threshold,
|
|
432
|
+
events: []
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
|
|
436
|
+
state.rest_counter += 1;
|
|
437
|
+
const events = [];
|
|
438
|
+
if (state.short_rest_enabled && nextRandom() < Math.max(0, Number(shortRestProbability) || 0)) {
|
|
439
|
+
const pauseMs = Math.round(randomBetween(nextRandom, shortRestMinMs, shortRestMaxMs));
|
|
440
|
+
await sleeper(pauseMs);
|
|
441
|
+
events.push({ kind: "random_rest", pause_ms: pauseMs });
|
|
442
|
+
}
|
|
443
|
+
if (state.batch_rest_enabled && state.rest_counter >= state.rest_threshold) {
|
|
444
|
+
const pauseMs = Math.round(randomBetween(nextRandom, batchRestMinMs, batchRestMaxMs));
|
|
445
|
+
await sleeper(pauseMs);
|
|
446
|
+
events.push({
|
|
447
|
+
kind: "batch_rest",
|
|
448
|
+
pause_ms: pauseMs,
|
|
449
|
+
processed_since_last_batch_rest: state.rest_counter
|
|
450
|
+
});
|
|
451
|
+
state.rest_counter = 0;
|
|
452
|
+
resetThreshold();
|
|
453
|
+
}
|
|
454
|
+
const pauseMs = events.reduce((sum, event) => sum + event.pause_ms, 0);
|
|
455
|
+
if (pauseMs > 0) {
|
|
456
|
+
state.rest_count += events.length;
|
|
457
|
+
state.total_rest_ms += pauseMs;
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
enabled: true,
|
|
461
|
+
rested: events.length > 0,
|
|
462
|
+
pause_ms: pauseMs,
|
|
463
|
+
rest_counter: state.rest_counter,
|
|
464
|
+
rest_threshold: state.rest_threshold,
|
|
465
|
+
rest_count: state.rest_count,
|
|
466
|
+
total_rest_ms: state.total_rest_ms,
|
|
467
|
+
events
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
takeBreakIfNeeded,
|
|
473
|
+
getState() {
|
|
474
|
+
return { ...state };
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
79
479
|
export function isBossLoginUrl(url) {
|
|
80
480
|
return BOSS_LOGIN_URL_PATTERN.test(String(url || ""));
|
|
81
481
|
}
|
|
@@ -692,15 +1092,179 @@ export async function getNodeBox(client, nodeId) {
|
|
|
692
1092
|
};
|
|
693
1093
|
}
|
|
694
1094
|
|
|
1095
|
+
export async function simulateHumanClick(client, targetX, targetY, {
|
|
1096
|
+
button = "left",
|
|
1097
|
+
clickCount = 1,
|
|
1098
|
+
delayMs = 80,
|
|
1099
|
+
random = Math.random,
|
|
1100
|
+
sleepFn = sleep,
|
|
1101
|
+
moveSteps = 18,
|
|
1102
|
+
moveJitterPx = 3,
|
|
1103
|
+
hoverJitterPx = 5,
|
|
1104
|
+
moveDelayMinMs = 5,
|
|
1105
|
+
moveDelayMaxMs = 23,
|
|
1106
|
+
hoverDelayMinMs = 10,
|
|
1107
|
+
hoverDelayMaxMs = 30,
|
|
1108
|
+
prePressBaseMs = 260,
|
|
1109
|
+
prePressVarianceMs = 80,
|
|
1110
|
+
holdVarianceMs = 30,
|
|
1111
|
+
startPoint = null
|
|
1112
|
+
} = {}) {
|
|
1113
|
+
const target = normalizePoint({ x: targetX, y: targetY });
|
|
1114
|
+
if (!target) throw new Error("simulateHumanClick requires finite target coordinates");
|
|
1115
|
+
const nextRandom = normalizeRandom(random);
|
|
1116
|
+
const interactionConfig = getHumanInteractionConfig(client) || {};
|
|
1117
|
+
const start = normalizePoint(startPoint)
|
|
1118
|
+
|| normalizePoint(interactionConfig.lastMousePoint)
|
|
1119
|
+
|| {
|
|
1120
|
+
x: Math.max(0, target.x + randomBetween(nextRandom, -140, 140)),
|
|
1121
|
+
y: Math.max(0, target.y + randomBetween(nextRandom, -90, 90))
|
|
1122
|
+
};
|
|
1123
|
+
const path = generateBezierPath(start, target, {
|
|
1124
|
+
steps: moveSteps,
|
|
1125
|
+
random: nextRandom
|
|
1126
|
+
});
|
|
1127
|
+
const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
|
|
1128
|
+
const moveDelayMin = Math.min(moveDelayMinMs, moveDelayMaxMs);
|
|
1129
|
+
const moveDelayMax = Math.max(moveDelayMinMs, moveDelayMaxMs);
|
|
1130
|
+
const hoverDelayMin = Math.min(hoverDelayMinMs, hoverDelayMaxMs);
|
|
1131
|
+
const hoverDelayMax = Math.max(hoverDelayMinMs, hoverDelayMaxMs);
|
|
1132
|
+
for (const point of path) {
|
|
1133
|
+
await client.Input.dispatchMouseEvent({
|
|
1134
|
+
type: "mouseMoved",
|
|
1135
|
+
x: Math.round(point.x + randomBetween(nextRandom, -moveJitterPx / 2, moveJitterPx / 2)),
|
|
1136
|
+
y: Math.round(point.y + randomBetween(nextRandom, -moveJitterPx / 2, moveJitterPx / 2)),
|
|
1137
|
+
button: "none"
|
|
1138
|
+
});
|
|
1139
|
+
const pauseMs = Math.round(randomBetween(nextRandom, moveDelayMin, moveDelayMax));
|
|
1140
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1141
|
+
}
|
|
1142
|
+
const hoverSteps = randomIntegerBetween(nextRandom, 3, 6);
|
|
1143
|
+
for (let index = 0; index < hoverSteps; index += 1) {
|
|
1144
|
+
await client.Input.dispatchMouseEvent({
|
|
1145
|
+
type: "mouseMoved",
|
|
1146
|
+
x: Math.round(target.x + randomBetween(nextRandom, -hoverJitterPx / 2, hoverJitterPx / 2)),
|
|
1147
|
+
y: Math.round(target.y + randomBetween(nextRandom, -hoverJitterPx / 2, hoverJitterPx / 2)),
|
|
1148
|
+
button: "none"
|
|
1149
|
+
});
|
|
1150
|
+
const pauseMs = Math.round(randomBetween(nextRandom, hoverDelayMin, hoverDelayMax));
|
|
1151
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1152
|
+
}
|
|
1153
|
+
const prePressMs = humanDelay(prePressBaseMs, prePressVarianceMs, {
|
|
1154
|
+
minMs: 0,
|
|
1155
|
+
maxMs: Math.max(prePressBaseMs + prePressVarianceMs * 4, prePressBaseMs),
|
|
1156
|
+
random: nextRandom
|
|
1157
|
+
});
|
|
1158
|
+
if (prePressMs > 0) await sleeper(prePressMs);
|
|
1159
|
+
await client.Input.dispatchMouseEvent({ type: "mousePressed", x: target.x, y: target.y, button, clickCount });
|
|
1160
|
+
const holdMs = humanDelay(delayMs, holdVarianceMs, {
|
|
1161
|
+
minMs: 0,
|
|
1162
|
+
maxMs: Math.max(delayMs + holdVarianceMs * 4, delayMs),
|
|
1163
|
+
random: nextRandom
|
|
1164
|
+
});
|
|
1165
|
+
if (holdMs > 0) await sleeper(holdMs);
|
|
1166
|
+
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x: target.x, y: target.y, button, clickCount });
|
|
1167
|
+
const latestConfig = getHumanInteractionConfig(client);
|
|
1168
|
+
if (latestConfig) latestConfig.lastMousePoint = target;
|
|
1169
|
+
return {
|
|
1170
|
+
mode: "human",
|
|
1171
|
+
path_points: path.length,
|
|
1172
|
+
hover_steps: hoverSteps,
|
|
1173
|
+
pre_press_ms: prePressMs,
|
|
1174
|
+
hold_ms: holdMs
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export function resolveHumanClickPointForBox(box, {
|
|
1179
|
+
enabled = true,
|
|
1180
|
+
safeClickPointEnabled = true,
|
|
1181
|
+
random = Math.random,
|
|
1182
|
+
safeClickMinWidth = 44,
|
|
1183
|
+
safeClickMinHeight = 28,
|
|
1184
|
+
safeClickInsetRatio = 0.22,
|
|
1185
|
+
safeClickMinInsetPx = 4,
|
|
1186
|
+
safeClickMaxInsetPx = 18
|
|
1187
|
+
} = {}) {
|
|
1188
|
+
const center = normalizePoint(box?.center);
|
|
1189
|
+
if (!center) throw new Error("resolveHumanClickPointForBox requires a box center");
|
|
1190
|
+
const rect = box?.rect || {};
|
|
1191
|
+
const width = Number(rect.width);
|
|
1192
|
+
const height = Number(rect.height);
|
|
1193
|
+
const originX = Number(rect.x);
|
|
1194
|
+
const originY = Number(rect.y);
|
|
1195
|
+
if (
|
|
1196
|
+
enabled !== true
|
|
1197
|
+
|| safeClickPointEnabled === false
|
|
1198
|
+
|| !Number.isFinite(width)
|
|
1199
|
+
|| !Number.isFinite(height)
|
|
1200
|
+
|| !Number.isFinite(originX)
|
|
1201
|
+
|| !Number.isFinite(originY)
|
|
1202
|
+
|| width < Math.max(1, Number(safeClickMinWidth) || 44)
|
|
1203
|
+
|| height < Math.max(1, Number(safeClickMinHeight) || 28)
|
|
1204
|
+
) {
|
|
1205
|
+
return {
|
|
1206
|
+
x: center.x,
|
|
1207
|
+
y: center.y,
|
|
1208
|
+
mode: "center",
|
|
1209
|
+
reason: "small_or_disabled"
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const nextRandom = normalizeRandom(random);
|
|
1214
|
+
const insetRatio = clampNumber(safeClickInsetRatio, 0.05, 0.45);
|
|
1215
|
+
const minInset = Math.max(0, Number(safeClickMinInsetPx) || 0);
|
|
1216
|
+
const maxInset = Math.max(minInset, Number(safeClickMaxInsetPx) || minInset);
|
|
1217
|
+
const insetX = Math.min(width / 2 - 1, Math.max(minInset, Math.min(maxInset, width * insetRatio)));
|
|
1218
|
+
const insetY = Math.min(height / 2 - 1, Math.max(minInset, Math.min(maxInset, height * insetRatio)));
|
|
1219
|
+
const usableWidth = Math.max(0, width - insetX * 2);
|
|
1220
|
+
const usableHeight = Math.max(0, height - insetY * 2);
|
|
1221
|
+
if (usableWidth <= 0 || usableHeight <= 0) {
|
|
1222
|
+
return {
|
|
1223
|
+
x: center.x,
|
|
1224
|
+
y: center.y,
|
|
1225
|
+
mode: "center",
|
|
1226
|
+
reason: "insufficient_safe_area"
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
x: originX + insetX + nextRandom() * usableWidth,
|
|
1231
|
+
y: originY + insetY + nextRandom() * usableHeight,
|
|
1232
|
+
mode: "safe_inset",
|
|
1233
|
+
inset_x: insetX,
|
|
1234
|
+
inset_y: insetY
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
695
1238
|
export async function clickPoint(client, x, y, {
|
|
696
1239
|
button = "left",
|
|
697
1240
|
clickCount = 1,
|
|
698
|
-
delayMs = 80
|
|
1241
|
+
delayMs = 80,
|
|
1242
|
+
humanRestEnabled = null,
|
|
1243
|
+
humanInteraction = null
|
|
699
1244
|
} = {}) {
|
|
1245
|
+
const configured = getHumanInteractionConfig(client);
|
|
1246
|
+
const mergedHumanInteraction = {
|
|
1247
|
+
...(configured || {}),
|
|
1248
|
+
...(humanInteraction || {})
|
|
1249
|
+
};
|
|
1250
|
+
const humanEnabled = humanRestEnabled === true
|
|
1251
|
+
|| humanInteraction?.enabled === true
|
|
1252
|
+
|| (humanRestEnabled !== false && configured?.enabled === true);
|
|
1253
|
+
if (humanEnabled && mergedHumanInteraction.clickMovementEnabled !== false) {
|
|
1254
|
+
return simulateHumanClick(client, x, y, {
|
|
1255
|
+
...mergedHumanInteraction,
|
|
1256
|
+
button,
|
|
1257
|
+
clickCount,
|
|
1258
|
+
delayMs
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
700
1261
|
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
701
1262
|
await client.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button, clickCount });
|
|
702
1263
|
if (delayMs > 0) await sleep(delayMs);
|
|
703
1264
|
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button, clickCount });
|
|
1265
|
+
return {
|
|
1266
|
+
mode: "direct"
|
|
1267
|
+
};
|
|
704
1268
|
}
|
|
705
1269
|
|
|
706
1270
|
export async function scrollNodeIntoView(client, nodeId) {
|
|
@@ -716,7 +1280,20 @@ export async function clickNodeCenter(client, nodeId, {
|
|
|
716
1280
|
await sleep(150);
|
|
717
1281
|
}
|
|
718
1282
|
const box = await getNodeBox(client, nodeId);
|
|
719
|
-
|
|
1283
|
+
const configured = getHumanInteractionConfig(client);
|
|
1284
|
+
const mergedHumanInteraction = {
|
|
1285
|
+
...(configured || {}),
|
|
1286
|
+
...(clickOptions.humanInteraction || {})
|
|
1287
|
+
};
|
|
1288
|
+
const humanClickPointEnabled = (
|
|
1289
|
+
clickOptions.humanRestEnabled === true
|
|
1290
|
+
|| clickOptions.humanInteraction?.enabled === true
|
|
1291
|
+
|| (clickOptions.humanRestEnabled !== false && configured?.enabled === true)
|
|
1292
|
+
) && mergedHumanInteraction.safeClickPointEnabled !== false;
|
|
1293
|
+
const clickPointTarget = humanClickPointEnabled
|
|
1294
|
+
? resolveHumanClickPointForBox(box, mergedHumanInteraction)
|
|
1295
|
+
: { ...box.center, mode: "center" };
|
|
1296
|
+
await clickPoint(client, clickPointTarget.x, clickPointTarget.y, clickOptions);
|
|
720
1297
|
return box;
|
|
721
1298
|
}
|
|
722
1299
|
|
|
@@ -746,8 +1323,79 @@ export async function pressKey(client, key, {
|
|
|
746
1323
|
});
|
|
747
1324
|
}
|
|
748
1325
|
|
|
749
|
-
export
|
|
750
|
-
|
|
1326
|
+
export function chunkHumanText(text, {
|
|
1327
|
+
random = Math.random,
|
|
1328
|
+
minLength = 1,
|
|
1329
|
+
maxLength = 5
|
|
1330
|
+
} = {}) {
|
|
1331
|
+
const chars = Array.from(String(text || ""));
|
|
1332
|
+
const min = Math.max(1, Math.floor(Number(minLength) || 1));
|
|
1333
|
+
const max = Math.max(min, Math.floor(Number(maxLength) || min));
|
|
1334
|
+
const nextRandom = normalizeRandom(random);
|
|
1335
|
+
const chunks = [];
|
|
1336
|
+
let index = 0;
|
|
1337
|
+
while (index < chars.length) {
|
|
1338
|
+
const remaining = chars.length - index;
|
|
1339
|
+
const size = Math.min(remaining, randomIntegerBetween(nextRandom, min, max));
|
|
1340
|
+
chunks.push(chars.slice(index, index + size).join(""));
|
|
1341
|
+
index += size;
|
|
1342
|
+
}
|
|
1343
|
+
return chunks;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
export async function insertText(client, text, {
|
|
1347
|
+
humanTextEntryEnabled = null,
|
|
1348
|
+
humanInteraction = null
|
|
1349
|
+
} = {}) {
|
|
1350
|
+
const value = String(text || "");
|
|
1351
|
+
const configured = getHumanInteractionConfig(client);
|
|
1352
|
+
const mergedHumanInteraction = {
|
|
1353
|
+
...(configured || {}),
|
|
1354
|
+
...(humanInteraction || {})
|
|
1355
|
+
};
|
|
1356
|
+
const textEntryEnabled = humanTextEntryEnabled === true
|
|
1357
|
+
|| humanInteraction?.textEntryEnabled === true
|
|
1358
|
+
|| (humanTextEntryEnabled !== false
|
|
1359
|
+
&& configured?.enabled === true
|
|
1360
|
+
&& configured?.textEntryEnabled !== false);
|
|
1361
|
+
if (!textEntryEnabled || value.length <= 1) {
|
|
1362
|
+
await client.Input.insertText({ text: value });
|
|
1363
|
+
return {
|
|
1364
|
+
mode: "direct",
|
|
1365
|
+
chunk_count: value ? 1 : 0
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
const chunks = chunkHumanText(value, {
|
|
1369
|
+
random: mergedHumanInteraction.random,
|
|
1370
|
+
minLength: mergedHumanInteraction.textChunkMinLength,
|
|
1371
|
+
maxLength: mergedHumanInteraction.textChunkMaxLength
|
|
1372
|
+
});
|
|
1373
|
+
const sleeper = typeof mergedHumanInteraction.sleepFn === "function"
|
|
1374
|
+
? mergedHumanInteraction.sleepFn
|
|
1375
|
+
: sleep;
|
|
1376
|
+
for (let index = 0; index < chunks.length; index += 1) {
|
|
1377
|
+
await client.Input.insertText({ text: chunks[index] });
|
|
1378
|
+
if (index < chunks.length - 1) {
|
|
1379
|
+
const pauseMs = humanDelay(
|
|
1380
|
+
mergedHumanInteraction.textChunkDelayBaseMs,
|
|
1381
|
+
mergedHumanInteraction.textChunkDelayVarianceMs,
|
|
1382
|
+
{
|
|
1383
|
+
minMs: 0,
|
|
1384
|
+
maxMs: Math.max(
|
|
1385
|
+
mergedHumanInteraction.textChunkDelayBaseMs + mergedHumanInteraction.textChunkDelayVarianceMs * 4,
|
|
1386
|
+
mergedHumanInteraction.textChunkDelayBaseMs
|
|
1387
|
+
),
|
|
1388
|
+
random: mergedHumanInteraction.random
|
|
1389
|
+
}
|
|
1390
|
+
);
|
|
1391
|
+
if (pauseMs > 0) await sleeper(pauseMs);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
mode: "chunked",
|
|
1396
|
+
chunk_count: chunks.length,
|
|
1397
|
+
chunks
|
|
1398
|
+
};
|
|
751
1399
|
}
|
|
752
1400
|
|
|
753
1401
|
export async function selectAllFocusedText(client) {
|