leapfrog-mcp 0.6.2 → 0.6.3
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/adaptive-wait.d.ts +72 -0
- package/dist/adaptive-wait.js +695 -0
- package/dist/api-intelligence.d.ts +82 -0
- package/dist/api-intelligence.js +575 -0
- package/dist/captcha-solver.d.ts +26 -0
- package/dist/captcha-solver.js +547 -0
- package/dist/cdp-connector.d.ts +33 -0
- package/dist/cdp-connector.js +176 -0
- package/dist/consent-dismiss.d.ts +33 -0
- package/dist/consent-dismiss.js +358 -0
- package/dist/crash-recovery.d.ts +74 -0
- package/dist/crash-recovery.js +242 -0
- package/dist/domain-knowledge.d.ts +149 -0
- package/dist/domain-knowledge.js +449 -0
- package/dist/harness-intelligence.d.ts +65 -0
- package/dist/harness-intelligence.js +432 -0
- package/dist/humanize-fingerprint.d.ts +42 -0
- package/dist/humanize-fingerprint.js +161 -0
- package/dist/humanize-mouse.d.ts +95 -0
- package/dist/humanize-mouse.js +275 -0
- package/dist/humanize-pause.d.ts +48 -0
- package/dist/humanize-pause.js +111 -0
- package/dist/humanize-scroll.d.ts +67 -0
- package/dist/humanize-scroll.js +185 -0
- package/dist/humanize-typing.d.ts +60 -0
- package/dist/humanize-typing.js +258 -0
- package/dist/humanize-utils.d.ts +62 -0
- package/dist/humanize-utils.js +100 -0
- package/dist/intervention.d.ts +65 -0
- package/dist/intervention.js +591 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +47 -0
- package/dist/network-intelligence.d.ts +70 -0
- package/dist/network-intelligence.js +424 -0
- package/dist/page-classifier.d.ts +33 -0
- package/dist/page-classifier.js +1000 -0
- package/dist/paginate.d.ts +42 -0
- package/dist/paginate.js +693 -0
- package/dist/recording.d.ts +72 -0
- package/dist/recording.js +934 -0
- package/dist/script-executor.d.ts +31 -0
- package/dist/script-executor.js +249 -0
- package/dist/session-hud.d.ts +20 -0
- package/dist/session-hud.js +134 -0
- package/dist/snapshot-differ.d.ts +26 -0
- package/dist/snapshot-differ.js +225 -0
- package/dist/ssrf.d.ts +28 -0
- package/dist/ssrf.js +290 -0
- package/dist/stealth-audit.d.ts +27 -0
- package/dist/stealth-audit.js +719 -0
- package/dist/stealth.d.ts +195 -0
- package/dist/stealth.js +1157 -0
- package/dist/tab-manager.d.ts +14 -0
- package/dist/tab-manager.js +306 -0
- package/dist/tiles-coordinator.d.ts +106 -0
- package/dist/tiles-coordinator.js +358 -0
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
export interface PathPoint extends Point {
|
|
7
|
+
/** Bezier parameter t in [0, 1] */
|
|
8
|
+
t: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Per-session motor profile. Generated randomly at class instantiation
|
|
12
|
+
* or via setProfile(). Modulates Fitts's Law constants, tremor magnitude,
|
|
13
|
+
* overshoot probability, and pause timing to give each session a
|
|
14
|
+
* consistent "personality" (some users are jittery, some are smooth).
|
|
15
|
+
*/
|
|
16
|
+
export interface MotorProfile {
|
|
17
|
+
/** Multiplier on Fitts's Law base time (0.7 = fast, 1.3 = slow). */
|
|
18
|
+
baseSpeed: number;
|
|
19
|
+
/** Multiplier on micro-tremor stddev (0.5 = steady, 2.0 = shaky). */
|
|
20
|
+
tremor: number;
|
|
21
|
+
/** Multiplier on overshoot probability (0.5 = precise, 1.5 = sloppy). */
|
|
22
|
+
overshootTendency: number;
|
|
23
|
+
/** Multiplier on dwell/pause durations (0.8 = impatient, 1.4 = deliberate). */
|
|
24
|
+
pauseMultiplier: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate a random motor profile. Each axis is drawn from a Gaussian
|
|
28
|
+
* centered at 1.0 so most sessions feel "average" with occasional outliers.
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateMotorProfile(): MotorProfile;
|
|
31
|
+
/**
|
|
32
|
+
* Evaluate a cubic Bezier curve at parameter t.
|
|
33
|
+
* B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3
|
|
34
|
+
*/
|
|
35
|
+
export declare function bezierPoint(t: number, p0: Point, p1: Point, p2: Point, p3: Point): Point;
|
|
36
|
+
/**
|
|
37
|
+
* Generate a human-like mouse path between two points using a cubic Bezier curve.
|
|
38
|
+
* Control points are randomly offset to simulate the natural arc of hand movement.
|
|
39
|
+
* Point spacing is non-uniform — uses asymmetric 40/60 velocity profile.
|
|
40
|
+
*
|
|
41
|
+
* Incorporates Fitts's Law: movement time scales with log2(distance/width + 1),
|
|
42
|
+
* so the number of steps increases for longer distances.
|
|
43
|
+
*
|
|
44
|
+
* @param start - Starting coordinates
|
|
45
|
+
* @param end - Ending coordinates
|
|
46
|
+
* @param steps - Number of intermediate points (0 = auto via Fitts's Law)
|
|
47
|
+
* @param profile - Optional motor profile for per-session variation
|
|
48
|
+
* @returns Array of path points with parameter t
|
|
49
|
+
*/
|
|
50
|
+
export declare function generateBezierPath(start: Point, end: Point, steps?: number, profile?: MotorProfile): PathPoint[];
|
|
51
|
+
export declare class HumanMouse {
|
|
52
|
+
/** Tracks the last known cursor position. Initialized lazily to a random viewport position. */
|
|
53
|
+
private lastPosition;
|
|
54
|
+
/** Per-session motor profile for consistent behavioral fingerprint. */
|
|
55
|
+
private profile;
|
|
56
|
+
constructor();
|
|
57
|
+
/**
|
|
58
|
+
* Whether humanized mouse movement is enabled.
|
|
59
|
+
* Returns false unless LEAP_HUMANIZE=true or LEAP_HUMANIZE=1 is set.
|
|
60
|
+
*/
|
|
61
|
+
isEnabled(): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Replace the current motor profile with a new random one.
|
|
64
|
+
* Call this at session rotation boundaries to vary behavior.
|
|
65
|
+
*/
|
|
66
|
+
setProfile(profile?: MotorProfile): void;
|
|
67
|
+
/** Get the current motor profile (for inspection/testing). */
|
|
68
|
+
getProfile(): MotorProfile;
|
|
69
|
+
/**
|
|
70
|
+
* Get the current cursor position. On first call, initializes to a
|
|
71
|
+
* random position within a plausible viewport region (avoids the (0,0)
|
|
72
|
+
* origin which is a known bot fingerprint).
|
|
73
|
+
*/
|
|
74
|
+
private getStartPosition;
|
|
75
|
+
/**
|
|
76
|
+
* Move the mouse along a human-like Bezier path to the target coordinates.
|
|
77
|
+
* Each step along the path is dispatched as a CDP mousemove event with a
|
|
78
|
+
* small inter-step delay to simulate real hand speed.
|
|
79
|
+
*
|
|
80
|
+
* Uses asymmetric 40/60 velocity profile, per-session motor profile,
|
|
81
|
+
* and probabilistic overshoot with correction.
|
|
82
|
+
*
|
|
83
|
+
* No-op if humanization is disabled.
|
|
84
|
+
*/
|
|
85
|
+
moveTo(page: Page, x: number, y: number): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Move the mouse to the target, pause briefly (hover dwell), then click.
|
|
88
|
+
* Simulates real human behavior: approach -> dwell -> click.
|
|
89
|
+
*
|
|
90
|
+
* No-op if humanization is disabled — falls through to normal click.
|
|
91
|
+
*/
|
|
92
|
+
humanClick(page: Page, x: number, y: number): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
export declare const humanMouse: HumanMouse;
|
|
95
|
+
export default humanMouse;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// ─── Humanized Mouse Movement ──────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Bezier curve mouse paths with Fitts's Law timing, asymmetric velocity
|
|
4
|
+
// profile, overshoot/correction, per-session motor profiles, micro-tremor
|
|
5
|
+
// jitter, and idle cursor drift.
|
|
6
|
+
//
|
|
7
|
+
// Integration point: import { humanMouse } from "./humanize-mouse.js"
|
|
8
|
+
// then call humanMouse.moveTo(page, x, y) or humanMouse.humanClick(page, x, y)
|
|
9
|
+
// inside the act tool handler (src/index.ts) before Playwright actions.
|
|
10
|
+
//
|
|
11
|
+
// Standalone module — no cross-dependencies on other humanize modules.
|
|
12
|
+
import { gaussianRandom, clamp, humanDelay, sleep, isHumanizeEnabled } from "./humanize-utils.js";
|
|
13
|
+
// ─── Motor Profile Generator ──────────────────────────────────────────────
|
|
14
|
+
/**
|
|
15
|
+
* Generate a random motor profile. Each axis is drawn from a Gaussian
|
|
16
|
+
* centered at 1.0 so most sessions feel "average" with occasional outliers.
|
|
17
|
+
*/
|
|
18
|
+
export function generateMotorProfile() {
|
|
19
|
+
return {
|
|
20
|
+
baseSpeed: clamp(gaussianRandom(1.0, 0.15), 0.6, 1.5),
|
|
21
|
+
tremor: clamp(gaussianRandom(1.0, 0.3), 0.3, 2.5),
|
|
22
|
+
overshootTendency: clamp(gaussianRandom(1.0, 0.25), 0.3, 2.0),
|
|
23
|
+
pauseMultiplier: clamp(gaussianRandom(1.0, 0.15), 0.6, 1.6),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// ─── Bezier Math ───────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Evaluate a cubic Bezier curve at parameter t.
|
|
29
|
+
* B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3
|
|
30
|
+
*/
|
|
31
|
+
export function bezierPoint(t, p0, p1, p2, p3) {
|
|
32
|
+
const u = 1 - t;
|
|
33
|
+
const tt = t * t;
|
|
34
|
+
const uu = u * u;
|
|
35
|
+
const uuu = uu * u;
|
|
36
|
+
const ttt = tt * t;
|
|
37
|
+
return {
|
|
38
|
+
x: uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x,
|
|
39
|
+
y: uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Asymmetric ease parameterization: 40% acceleration / 60% deceleration.
|
|
44
|
+
* Human deceleration is longer than acceleration due to corrective
|
|
45
|
+
* submovements near the target (Woodworth 1899, Elliott et al. 2001).
|
|
46
|
+
*
|
|
47
|
+
* - t in [0, 0.4]: quadratic ease-in (accelerating)
|
|
48
|
+
* - t in [0.4, 1]: quadratic ease-out (decelerating, stretched over 60%)
|
|
49
|
+
*
|
|
50
|
+
* Returns a value in [0, 1] that maps linearly-spaced t to non-uniform
|
|
51
|
+
* Bezier parameter values.
|
|
52
|
+
*/
|
|
53
|
+
function asymmetricEase(linearT) {
|
|
54
|
+
if (linearT <= 0.4) {
|
|
55
|
+
// Ease-in: normalize to [0,1] within the accel phase, quadratic ramp-up
|
|
56
|
+
const phase = linearT / 0.4; // 0..1
|
|
57
|
+
// At phase=1, output should be 0.5 (halfway through curve)
|
|
58
|
+
return 0.5 * phase * phase;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Ease-out: normalize to [0,1] within the decel phase, quadratic ramp-down
|
|
62
|
+
const phase = (linearT - 0.4) / 0.6; // 0..1
|
|
63
|
+
// At phase=0 output=0.5, at phase=1 output=1.0
|
|
64
|
+
return 0.5 + 0.5 * (1 - (1 - phase) * (1 - phase));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generate a human-like mouse path between two points using a cubic Bezier curve.
|
|
69
|
+
* Control points are randomly offset to simulate the natural arc of hand movement.
|
|
70
|
+
* Point spacing is non-uniform — uses asymmetric 40/60 velocity profile.
|
|
71
|
+
*
|
|
72
|
+
* Incorporates Fitts's Law: movement time scales with log2(distance/width + 1),
|
|
73
|
+
* so the number of steps increases for longer distances.
|
|
74
|
+
*
|
|
75
|
+
* @param start - Starting coordinates
|
|
76
|
+
* @param end - Ending coordinates
|
|
77
|
+
* @param steps - Number of intermediate points (0 = auto via Fitts's Law)
|
|
78
|
+
* @param profile - Optional motor profile for per-session variation
|
|
79
|
+
* @returns Array of path points with parameter t
|
|
80
|
+
*/
|
|
81
|
+
export function generateBezierPath(start, end, steps = 0, profile) {
|
|
82
|
+
const dx = end.x - start.x;
|
|
83
|
+
const dy = end.y - start.y;
|
|
84
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
85
|
+
const speedMul = profile?.baseSpeed ?? 1.0;
|
|
86
|
+
const tremorMul = profile?.tremor ?? 1.0;
|
|
87
|
+
// Fitts's Law: time = a + b * log2(distance / width + 1)
|
|
88
|
+
// We use this to auto-scale the number of steps for realism.
|
|
89
|
+
if (steps <= 0) {
|
|
90
|
+
const fittsTime = (150 + 120 * Math.log2(distance / 10 + 1)) * speedMul; // ms
|
|
91
|
+
steps = Math.max(10, Math.round(fittsTime / 8)); // ~8ms per step (125 fps)
|
|
92
|
+
}
|
|
93
|
+
// Generate control points with random lateral offset (perpendicular to the line).
|
|
94
|
+
// Offset magnitude is proportional to distance but capped for short moves.
|
|
95
|
+
const spread = clamp(distance * 0.3, 20, 300);
|
|
96
|
+
const angle = Math.atan2(dy, dx);
|
|
97
|
+
const perpX = -Math.sin(angle);
|
|
98
|
+
const perpY = Math.cos(angle);
|
|
99
|
+
const offset1 = gaussianRandom(0, spread);
|
|
100
|
+
const offset2 = gaussianRandom(0, spread);
|
|
101
|
+
const p1 = {
|
|
102
|
+
x: start.x + dx * 0.25 + perpX * offset1,
|
|
103
|
+
y: start.y + dy * 0.25 + perpY * offset1,
|
|
104
|
+
};
|
|
105
|
+
const p2 = {
|
|
106
|
+
x: start.x + dx * 0.75 + perpX * offset2,
|
|
107
|
+
y: start.y + dy * 0.75 + perpY * offset2,
|
|
108
|
+
};
|
|
109
|
+
const path = [];
|
|
110
|
+
for (let i = 0; i <= steps; i++) {
|
|
111
|
+
// Asymmetric velocity profile: 40% accel, 60% decel
|
|
112
|
+
const linearT = i / steps;
|
|
113
|
+
const easedT = asymmetricEase(linearT);
|
|
114
|
+
const pt = bezierPoint(easedT, start, p1, p2, end);
|
|
115
|
+
path.push({
|
|
116
|
+
x: Math.round(pt.x * 10) / 10,
|
|
117
|
+
y: Math.round(pt.y * 10) / 10,
|
|
118
|
+
t: Math.round(easedT * 1000) / 1000,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// Add small jitter to intermediate points (micro-tremor simulation)
|
|
122
|
+
const tremorStddev = 0.8 * tremorMul;
|
|
123
|
+
for (let i = 1; i < path.length - 1; i++) {
|
|
124
|
+
path[i].x += gaussianRandom(0, tremorStddev);
|
|
125
|
+
path[i].y += gaussianRandom(0, tremorStddev);
|
|
126
|
+
path[i].x = Math.round(path[i].x * 10) / 10;
|
|
127
|
+
path[i].y = Math.round(path[i].y * 10) / 10;
|
|
128
|
+
}
|
|
129
|
+
return path;
|
|
130
|
+
}
|
|
131
|
+
// ─── Overshoot Logic ──────────────────────────────────────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Determine whether an overshoot should occur based on movement distance,
|
|
134
|
+
* and generate the overshoot + correction path if so.
|
|
135
|
+
*
|
|
136
|
+
* Overshoot probabilities (before overshootTendency multiplier):
|
|
137
|
+
* - Distance < 100px: 10%
|
|
138
|
+
* - Distance 100-400px: 25%
|
|
139
|
+
* - Distance > 400px: 40%
|
|
140
|
+
*
|
|
141
|
+
* Overshoot amplitude: 5-15px past target along the approach direction.
|
|
142
|
+
* Correction movement: 3-5x slower than approach speed.
|
|
143
|
+
*/
|
|
144
|
+
function generateOvershoot(target, approachAngle, distance, profile) {
|
|
145
|
+
// Determine probability
|
|
146
|
+
let baseProbability;
|
|
147
|
+
if (distance < 100)
|
|
148
|
+
baseProbability = 0.10;
|
|
149
|
+
else if (distance <= 400)
|
|
150
|
+
baseProbability = 0.25;
|
|
151
|
+
else
|
|
152
|
+
baseProbability = 0.40;
|
|
153
|
+
const probability = clamp(baseProbability * (profile?.overshootTendency ?? 1.0), 0, 0.95);
|
|
154
|
+
if (Math.random() > probability)
|
|
155
|
+
return null;
|
|
156
|
+
// Overshoot amplitude: 5-15px past target along the approach direction
|
|
157
|
+
const amplitude = 5 + Math.random() * 10;
|
|
158
|
+
const overshootPoint = {
|
|
159
|
+
x: target.x + Math.cos(approachAngle) * amplitude,
|
|
160
|
+
y: target.y + Math.sin(approachAngle) * amplitude,
|
|
161
|
+
};
|
|
162
|
+
// Generate a short overshoot path (few steps, quick)
|
|
163
|
+
const overshootPath = generateBezierPath(target, overshootPoint, 5, profile);
|
|
164
|
+
// Correction path back to target: 3-5x more steps (slower)
|
|
165
|
+
const correctionSlowdown = 3 + Math.random() * 2;
|
|
166
|
+
const correctionSteps = Math.round(5 * correctionSlowdown);
|
|
167
|
+
const correctionPath = generateBezierPath(overshootPoint, target, correctionSteps, profile);
|
|
168
|
+
return { overshootPath, correctionPath };
|
|
169
|
+
}
|
|
170
|
+
// ─── HumanMouse Class ──────────────────────────────────────────────────────
|
|
171
|
+
export class HumanMouse {
|
|
172
|
+
/** Tracks the last known cursor position. Initialized lazily to a random viewport position. */
|
|
173
|
+
lastPosition = null;
|
|
174
|
+
/** Per-session motor profile for consistent behavioral fingerprint. */
|
|
175
|
+
profile;
|
|
176
|
+
constructor() {
|
|
177
|
+
this.profile = generateMotorProfile();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Whether humanized mouse movement is enabled.
|
|
181
|
+
* Returns false unless LEAP_HUMANIZE=true or LEAP_HUMANIZE=1 is set.
|
|
182
|
+
*/
|
|
183
|
+
isEnabled() {
|
|
184
|
+
return isHumanizeEnabled();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Replace the current motor profile with a new random one.
|
|
188
|
+
* Call this at session rotation boundaries to vary behavior.
|
|
189
|
+
*/
|
|
190
|
+
setProfile(profile) {
|
|
191
|
+
this.profile = profile ?? generateMotorProfile();
|
|
192
|
+
}
|
|
193
|
+
/** Get the current motor profile (for inspection/testing). */
|
|
194
|
+
getProfile() {
|
|
195
|
+
return { ...this.profile };
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the current cursor position. On first call, initializes to a
|
|
199
|
+
* random position within a plausible viewport region (avoids the (0,0)
|
|
200
|
+
* origin which is a known bot fingerprint).
|
|
201
|
+
*/
|
|
202
|
+
getStartPosition() {
|
|
203
|
+
if (!this.lastPosition) {
|
|
204
|
+
this.lastPosition = {
|
|
205
|
+
x: 400 + Math.random() * 400, // 400-800
|
|
206
|
+
y: 300 + Math.random() * 200, // 300-500
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return this.lastPosition;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Move the mouse along a human-like Bezier path to the target coordinates.
|
|
213
|
+
* Each step along the path is dispatched as a CDP mousemove event with a
|
|
214
|
+
* small inter-step delay to simulate real hand speed.
|
|
215
|
+
*
|
|
216
|
+
* Uses asymmetric 40/60 velocity profile, per-session motor profile,
|
|
217
|
+
* and probabilistic overshoot with correction.
|
|
218
|
+
*
|
|
219
|
+
* No-op if humanization is disabled.
|
|
220
|
+
*/
|
|
221
|
+
async moveTo(page, x, y) {
|
|
222
|
+
if (!this.isEnabled())
|
|
223
|
+
return;
|
|
224
|
+
const start = this.getStartPosition();
|
|
225
|
+
const end = { x, y };
|
|
226
|
+
const dx = end.x - start.x;
|
|
227
|
+
const dy = end.y - start.y;
|
|
228
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
229
|
+
const approachAngle = Math.atan2(dy, dx);
|
|
230
|
+
// Main movement path
|
|
231
|
+
const path = generateBezierPath(start, end, 0, this.profile);
|
|
232
|
+
for (const pt of path) {
|
|
233
|
+
await page.mouse.move(pt.x, pt.y);
|
|
234
|
+
// ~8ms inter-step for natural pacing (matching 125fps assumption)
|
|
235
|
+
await sleep(humanDelay(4, 12));
|
|
236
|
+
}
|
|
237
|
+
// Probabilistic overshoot + correction
|
|
238
|
+
const overshoot = generateOvershoot(end, approachAngle, distance, this.profile);
|
|
239
|
+
if (overshoot) {
|
|
240
|
+
// Overshoot: move past the target
|
|
241
|
+
for (const pt of overshoot.overshootPath) {
|
|
242
|
+
await page.mouse.move(pt.x, pt.y);
|
|
243
|
+
await sleep(humanDelay(4, 12));
|
|
244
|
+
}
|
|
245
|
+
// Correction: move back to target, 3-5x slower
|
|
246
|
+
const correctionDelay = humanDelay(4, 12) * (3 + Math.random() * 2);
|
|
247
|
+
for (const pt of overshoot.correctionPath) {
|
|
248
|
+
await page.mouse.move(pt.x, pt.y);
|
|
249
|
+
await sleep(correctionDelay);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Update last known position
|
|
253
|
+
this.lastPosition = { x, y };
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Move the mouse to the target, pause briefly (hover dwell), then click.
|
|
257
|
+
* Simulates real human behavior: approach -> dwell -> click.
|
|
258
|
+
*
|
|
259
|
+
* No-op if humanization is disabled — falls through to normal click.
|
|
260
|
+
*/
|
|
261
|
+
async humanClick(page, x, y) {
|
|
262
|
+
if (!this.isEnabled())
|
|
263
|
+
return;
|
|
264
|
+
await this.moveTo(page, x, y);
|
|
265
|
+
// Hover dwell: humans pause 50-200ms before clicking, modulated by motor profile
|
|
266
|
+
const dwellMin = Math.round(50 * this.profile.pauseMultiplier);
|
|
267
|
+
const dwellMax = Math.round(200 * this.profile.pauseMultiplier);
|
|
268
|
+
await sleep(humanDelay(dwellMin, dwellMax));
|
|
269
|
+
await page.mouse.click(x, y);
|
|
270
|
+
// Update position after click
|
|
271
|
+
this.lastPosition = { x, y };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
export const humanMouse = new HumanMouse();
|
|
275
|
+
export default humanMouse;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Action types that trigger different think-pause durations. */
|
|
2
|
+
export type ActionType = "click" | "type" | "scroll" | "navigate";
|
|
3
|
+
export declare class ThinkPause {
|
|
4
|
+
/**
|
|
5
|
+
* Timestamp of the last navigation completion. Used to enforce the
|
|
6
|
+
* post-navigation settling delay.
|
|
7
|
+
*/
|
|
8
|
+
private lastNavigationTime;
|
|
9
|
+
/**
|
|
10
|
+
* Whether the first interaction after the latest navigation has occurred.
|
|
11
|
+
* Reset on each navigation, set to true after the first beforeAction call.
|
|
12
|
+
*/
|
|
13
|
+
private firstInteractionDone;
|
|
14
|
+
/**
|
|
15
|
+
* Whether think pauses are enabled.
|
|
16
|
+
* Returns false unless LEAP_HUMANIZE=true or LEAP_HUMANIZE=1 is set.
|
|
17
|
+
*/
|
|
18
|
+
isEnabled(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Wait a human-like amount of time before performing an action.
|
|
21
|
+
* The delay is drawn from a Gaussian distribution within the range
|
|
22
|
+
* appropriate for the given action type.
|
|
23
|
+
*
|
|
24
|
+
* If this is the first interaction after a navigation, an additional
|
|
25
|
+
* post-navigation settling delay is enforced (min 500ms, log-normal
|
|
26
|
+
* median 1.5s).
|
|
27
|
+
*
|
|
28
|
+
* No-op if humanization is disabled.
|
|
29
|
+
*
|
|
30
|
+
* @param actionType - The type of action about to be performed
|
|
31
|
+
* @returns The actual delay waited in ms (0 if disabled)
|
|
32
|
+
*/
|
|
33
|
+
beforeAction(actionType: ActionType): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Signal that a navigation has completed. Must be called after each
|
|
36
|
+
* page navigation to arm the post-navigation settling logic.
|
|
37
|
+
*
|
|
38
|
+
* The next call to beforeAction() will enforce a minimum 500ms delay
|
|
39
|
+
* with a log-normal first-interaction pause (median 1.5s).
|
|
40
|
+
*
|
|
41
|
+
* No-op if humanization is disabled.
|
|
42
|
+
*
|
|
43
|
+
* @returns The settling delay that will be enforced (0 if disabled)
|
|
44
|
+
*/
|
|
45
|
+
afterNavigation(): number;
|
|
46
|
+
}
|
|
47
|
+
export declare const thinkPause: ThinkPause;
|
|
48
|
+
export default thinkPause;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// ─── Humanized Think Pauses ────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Inter-action "think" delays that simulate the cognitive gap between
|
|
4
|
+
// deciding what to do and actually doing it. Real humans don't chain
|
|
5
|
+
// click-type-scroll at machine speed — there's a reaction time gap.
|
|
6
|
+
//
|
|
7
|
+
// Also includes post-navigation settling, content-aware dwell time,
|
|
8
|
+
// and form-fill timing helpers.
|
|
9
|
+
//
|
|
10
|
+
// Integration point: import { thinkPause } from "./humanize-pause.js"
|
|
11
|
+
// then call thinkPause.beforeAction("click") inside the act tool handler
|
|
12
|
+
// (src/index.ts) before dispatching each Playwright action.
|
|
13
|
+
//
|
|
14
|
+
// Standalone module — no cross-dependencies on other humanize modules.
|
|
15
|
+
import { humanDelay, logNormalDelay, sleep, isHumanizeEnabled } from "./humanize-utils.js";
|
|
16
|
+
// ─── Delay Ranges ──────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Cognitive delay ranges based on action type.
|
|
19
|
+
*
|
|
20
|
+
* These ranges are calibrated from HCI research on human reaction times:
|
|
21
|
+
* - Click: quick motor action, 100-400ms (visual target acquisition)
|
|
22
|
+
* - Type: mental composition before typing, 200-600ms
|
|
23
|
+
* - Scroll: rapid decision, 50-200ms (continuation gesture)
|
|
24
|
+
* - Navigate: page comprehension pause, 500-1500ms (after page load)
|
|
25
|
+
*/
|
|
26
|
+
const DELAY_RANGES = {
|
|
27
|
+
click: { min: 100, max: 400 },
|
|
28
|
+
type: { min: 200, max: 600 },
|
|
29
|
+
scroll: { min: 50, max: 200 },
|
|
30
|
+
navigate: { min: 500, max: 1500 },
|
|
31
|
+
};
|
|
32
|
+
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
33
|
+
/** Minimum post-navigation settling time in ms. */
|
|
34
|
+
const NAV_SETTLE_FLOOR_MS = 500;
|
|
35
|
+
/** Median first-interaction delay after navigation (log-normal center). */
|
|
36
|
+
const NAV_FIRST_INTERACTION_MEDIAN_MS = 1500;
|
|
37
|
+
// ─── ThinkPause Class ──────────────────────────────────────────────────────
|
|
38
|
+
export class ThinkPause {
|
|
39
|
+
/**
|
|
40
|
+
* Timestamp of the last navigation completion. Used to enforce the
|
|
41
|
+
* post-navigation settling delay.
|
|
42
|
+
*/
|
|
43
|
+
lastNavigationTime = 0;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the first interaction after the latest navigation has occurred.
|
|
46
|
+
* Reset on each navigation, set to true after the first beforeAction call.
|
|
47
|
+
*/
|
|
48
|
+
firstInteractionDone = true;
|
|
49
|
+
/**
|
|
50
|
+
* Whether think pauses are enabled.
|
|
51
|
+
* Returns false unless LEAP_HUMANIZE=true or LEAP_HUMANIZE=1 is set.
|
|
52
|
+
*/
|
|
53
|
+
isEnabled() {
|
|
54
|
+
return isHumanizeEnabled();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Wait a human-like amount of time before performing an action.
|
|
58
|
+
* The delay is drawn from a Gaussian distribution within the range
|
|
59
|
+
* appropriate for the given action type.
|
|
60
|
+
*
|
|
61
|
+
* If this is the first interaction after a navigation, an additional
|
|
62
|
+
* post-navigation settling delay is enforced (min 500ms, log-normal
|
|
63
|
+
* median 1.5s).
|
|
64
|
+
*
|
|
65
|
+
* No-op if humanization is disabled.
|
|
66
|
+
*
|
|
67
|
+
* @param actionType - The type of action about to be performed
|
|
68
|
+
* @returns The actual delay waited in ms (0 if disabled)
|
|
69
|
+
*/
|
|
70
|
+
async beforeAction(actionType) {
|
|
71
|
+
if (!this.isEnabled())
|
|
72
|
+
return 0;
|
|
73
|
+
let totalDelay = 0;
|
|
74
|
+
// Post-navigation settling: enforce minimum 500ms after navigation
|
|
75
|
+
if (!this.firstInteractionDone) {
|
|
76
|
+
this.firstInteractionDone = true;
|
|
77
|
+
const elapsed = Date.now() - this.lastNavigationTime;
|
|
78
|
+
const settleDelay = logNormalDelay(NAV_FIRST_INTERACTION_MEDIAN_MS, 0.4, NAV_SETTLE_FLOOR_MS);
|
|
79
|
+
const remaining = Math.max(0, settleDelay - elapsed);
|
|
80
|
+
if (remaining > 0) {
|
|
81
|
+
await sleep(remaining);
|
|
82
|
+
totalDelay += remaining;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const range = DELAY_RANGES[actionType] ?? DELAY_RANGES.click;
|
|
86
|
+
const delay = humanDelay(range.min, range.max);
|
|
87
|
+
await sleep(delay);
|
|
88
|
+
totalDelay += delay;
|
|
89
|
+
return totalDelay;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Signal that a navigation has completed. Must be called after each
|
|
93
|
+
* page navigation to arm the post-navigation settling logic.
|
|
94
|
+
*
|
|
95
|
+
* The next call to beforeAction() will enforce a minimum 500ms delay
|
|
96
|
+
* with a log-normal first-interaction pause (median 1.5s).
|
|
97
|
+
*
|
|
98
|
+
* No-op if humanization is disabled.
|
|
99
|
+
*
|
|
100
|
+
* @returns The settling delay that will be enforced (0 if disabled)
|
|
101
|
+
*/
|
|
102
|
+
afterNavigation() {
|
|
103
|
+
if (!this.isEnabled())
|
|
104
|
+
return 0;
|
|
105
|
+
this.lastNavigationTime = Date.now();
|
|
106
|
+
this.firstInteractionDone = false;
|
|
107
|
+
return NAV_SETTLE_FLOOR_MS;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export const thinkPause = new ThinkPause();
|
|
111
|
+
export default thinkPause;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
export interface ScrollStep {
|
|
3
|
+
/** Scroll increment in pixels (positive = down, negative = up) */
|
|
4
|
+
delta: number;
|
|
5
|
+
/** Delay before this step in ms */
|
|
6
|
+
delay: number;
|
|
7
|
+
/** Cumulative scroll distance after this step */
|
|
8
|
+
cumulative: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ScrollOptions {
|
|
11
|
+
/** Maximum single scroll increment in pixels. Default: 120 */
|
|
12
|
+
maxIncrement?: number;
|
|
13
|
+
/** Momentum decay factor per step (0-1). Default: 0.82 */
|
|
14
|
+
friction?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate a human-like scroll plan for a given pixel distance.
|
|
18
|
+
* Simulates inertial scrolling with ease-out momentum decay.
|
|
19
|
+
*
|
|
20
|
+
* The scroll is broken into increments that start small (ramp-up / finger
|
|
21
|
+
* contact), hit peak velocity, then taper off (friction / momentum loss),
|
|
22
|
+
* similar to real touchpad scrolling.
|
|
23
|
+
*
|
|
24
|
+
* @param distance - Total scroll distance in pixels (positive = down)
|
|
25
|
+
* @param opts - Configuration options
|
|
26
|
+
* @returns Array of scroll steps with deltas and delays
|
|
27
|
+
*/
|
|
28
|
+
export declare function humanScrollPlan(distance: number, opts?: ScrollOptions): ScrollStep[];
|
|
29
|
+
export declare class HumanScroll {
|
|
30
|
+
/**
|
|
31
|
+
* Whether humanized scrolling is enabled.
|
|
32
|
+
* Returns false unless LEAP_HUMANIZE=true or LEAP_HUMANIZE=1 is set.
|
|
33
|
+
*/
|
|
34
|
+
isEnabled(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Scroll the page by the given pixel distance using a human-like
|
|
37
|
+
* ramp-up + momentum decay pattern.
|
|
38
|
+
*
|
|
39
|
+
* Each step in the scroll plan is dispatched as a mouse wheel event
|
|
40
|
+
* with an inter-step delay to simulate real scrolling inertia.
|
|
41
|
+
*
|
|
42
|
+
* Enhancements over basic scroll:
|
|
43
|
+
* - Variable scroll amounts (normalRandom instead of fixed)
|
|
44
|
+
* - Read-pause-scroll cycles (800-3000ms reading gaps between chunks)
|
|
45
|
+
* - 10% chance of overshoot + scroll-back correction
|
|
46
|
+
* - Sympathetic cursor micro-movements during scrolling
|
|
47
|
+
*
|
|
48
|
+
* No-op if humanization is disabled.
|
|
49
|
+
*
|
|
50
|
+
* @param page - Playwright page instance
|
|
51
|
+
* @param distance - Total scroll distance in pixels (positive = down, negative = up)
|
|
52
|
+
*/
|
|
53
|
+
scroll(page: Page, distance: number): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Scroll with variable distance. Uses normalRandom(350, 100) to determine
|
|
56
|
+
* a natural scroll distance, then executes the humanized scroll.
|
|
57
|
+
*
|
|
58
|
+
* Useful when the caller just wants "scroll down some" without a specific
|
|
59
|
+
* pixel target.
|
|
60
|
+
*
|
|
61
|
+
* @param page - Playwright page instance
|
|
62
|
+
* @param direction - "down" (positive) or "up" (negative)
|
|
63
|
+
*/
|
|
64
|
+
scrollVariable(page: Page, direction?: "down" | "up"): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
export declare const humanScroll: HumanScroll;
|
|
67
|
+
export default humanScroll;
|