@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.
@@ -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
- await clickPoint(client, box.center.x, box.center.y, clickOptions);
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 async function insertText(client, text) {
750
- await client.Input.insertText({ text: String(text || "") });
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) {