@reconcrap/boss-recommend-mcp 2.0.42 → 2.0.44

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