captchakraken 2.9.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +112 -0
- package/NOTICE +36 -0
- package/dist/cli-invocation.d.ts +7 -36
- package/dist/cli-invocation.js +8 -49
- package/dist/errors.d.ts +7 -27
- package/dist/errors.js +6 -25
- package/dist/humanize.d.ts +56 -240
- package/dist/humanize.js +159 -415
- package/dist/index.d.ts +5 -129
- package/dist/index.js +4 -98
- package/dist/kinds.d.ts +135 -0
- package/dist/kinds.js +70 -0
- package/dist/limits.d.ts +0 -48
- package/dist/limits.js +2 -48
- package/dist/model-name.d.ts +4 -19
- package/dist/model-name.js +32 -40
- package/dist/playwright-types.d.ts +25 -95
- package/dist/playwright-types.js +0 -23
- package/dist/puppeteer-adapter.d.ts +4 -43
- package/dist/puppeteer-adapter.js +22 -58
- package/dist/python-command.d.ts +0 -41
- package/dist/python-command.js +0 -37
- package/dist/selectors.d.ts +54 -0
- package/dist/selectors.js +117 -0
- package/dist/slide-geometry.d.ts +2 -29
- package/dist/slide-geometry.js +18 -44
- package/dist/solver.d.ts +94 -712
- package/dist/solver.js +923 -3362
- package/dist/timing.d.ts +7 -40
- package/dist/timing.js +4 -36
- package/dist/token-usage.d.ts +1 -7
- package/dist/token-usage.js +10 -95
- package/dist/types.d.ts +75 -493
- package/dist/watcher.d.ts +5 -78
- package/dist/watcher.js +0 -56
- package/package.json +11 -4
- package/python/AGENTS.md +122 -0
- package/python/NOTICE +36 -0
- package/python/README.md +1 -2
- package/python/examples/README.md +10 -0
- package/python/examples/_harness.py +4 -73
- package/python/examples/demoHcaptcha.py +0 -13
- package/python/examples/demoRecaptcha.py +0 -10
- package/python/examples/watch_playwright.py +1 -26
- package/python/examples/with_local_gguf.py +28 -0
- package/python/examples/with_playwright.py +0 -19
- package/python/pyproject.toml +68 -19
- package/python/src/captchakraken/__init__.py +37 -45
- package/python/src/captchakraken/action_types.py +14 -36
- package/python/src/captchakraken/cli.py +262 -883
- package/python/src/captchakraken/config.py +60 -146
- package/python/src/captchakraken/errors.py +17 -95
- package/python/src/captchakraken/humanize.py +174 -597
- package/python/src/captchakraken/image_processor.py +16 -235
- package/python/src/captchakraken/keyframes.py +46 -484
- package/python/src/captchakraken/kinds.py +152 -0
- package/python/src/captchakraken/models.json +36 -4
- package/python/src/captchakraken/overlay.py +65 -498
- package/python/src/captchakraken/page_solver.py +1052 -3098
- package/python/src/captchakraken/planner.py +50 -487
- package/python/src/captchakraken/prompts.py +45 -261
- package/python/src/captchakraken/selectors.py +184 -0
- package/python/src/captchakraken/server_manager.py +7 -58
- package/python/src/captchakraken/solver.py +128 -522
- package/python/src/captchakraken/timing.py +20 -74
- package/python/src/captchakraken/tool_calls/board_painted.py +47 -0
- package/python/src/captchakraken/tool_calls/find_checkbox.py +20 -30
- package/python/src/captchakraken/tool_calls/find_grid.py +186 -1339
- package/python/src/captchakraken/tool_calls/track_piece.py +81 -57
- package/python/src/captchakraken/updater.py +7 -76
- package/python/src/captchakraken/watcher.py +12 -101
- package/scripts/copy-python.mjs +10 -10
- package/scripts/setup-python.js +1 -21
- package/dist/trajectory.d.ts +0 -58
- package/dist/trajectory.js +0 -266
- package/python/src/captchakraken/tool_calls/move_indicator.py +0 -431
- package/python/src/captchakraken/trajectory.py +0 -284
package/dist/types.d.ts
CHANGED
|
@@ -1,510 +1,127 @@
|
|
|
1
|
-
import type { Humanizer,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* before any interaction (`stage: 'initial'`) and one after every executed
|
|
5
|
-
* action (click batch, drag, wait, submit) plus per dynamic-grid round, so a
|
|
6
|
-
* caller can record the exact sequence of intermediate stages, time them, and
|
|
7
|
-
* count steps without scraping CAPTCHA_DEBUG dumps.
|
|
8
|
-
*/
|
|
1
|
+
import type { Humanizer, TouchTransform } from './humanize.js';
|
|
2
|
+
import type { ActionKind, FrameRole, HumanizationMode, PromptFamily, SolveStage, Vendor } from './kinds';
|
|
3
|
+
/** One intermediate stage of a solve, handed to `CaptchaKrakenConfig.onStep`. */
|
|
9
4
|
export interface SolveStepEvent {
|
|
10
|
-
/** 1-based
|
|
5
|
+
/** 1-based, increasing across the whole solve. */
|
|
11
6
|
index: number;
|
|
12
|
-
|
|
13
|
-
* Coarse stage kind:
|
|
14
|
-
* - `initial` : screenshot taken before any action (baseline)
|
|
15
|
-
* - `click` : after a click (or batch of clicks) was executed
|
|
16
|
-
* - `drag` : after a drag was executed (a slider counts as one)
|
|
17
|
-
* - `type` : after a distorted-text answer was typed into its box
|
|
18
|
-
* - `wait` : after a CLI-requested wait elapsed
|
|
19
|
-
* - `submit` : after the Verify/Next/Submit button was clicked
|
|
20
|
-
* - `round` : a dynamic reCAPTCHA 3x3 round boundary (pre-solve snapshot)
|
|
21
|
-
*/
|
|
22
|
-
stage: 'initial' | 'click' | 'drag' | 'type' | 'wait' | 'submit' | 'round';
|
|
23
|
-
/** Short human label, e.g. "round-2:clicked 3 tile(s)". */
|
|
7
|
+
stage: SolveStage;
|
|
24
8
|
label: string;
|
|
25
|
-
/**
|
|
26
|
-
* Absolute path to a PNG screenshot of the captcha element at this step.
|
|
27
|
-
* The file is owned by the callback once emitted — the solver does NOT
|
|
28
|
-
* delete it (copy/move it where you need it). Null if the screenshot failed.
|
|
29
|
-
*/
|
|
9
|
+
/** PNG of the captcha element at this step; owned by the callback. Null if the screenshot failed. */
|
|
30
10
|
screenshotPath: string | null;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Which captcha frame this screenshot is of:
|
|
35
|
-
* - `checkbox` : the anchor "I'm not a robot" widget (no puzzle yet)
|
|
36
|
-
* - `challenge` : the open image/grid challenge frame (the real puzzle)
|
|
37
|
-
* - `unknown` : could not be determined from the frame src
|
|
38
|
-
* Recorders that only want the actual solve (not the pre-challenge checkbox
|
|
39
|
-
* clicks) filter to `challenge`.
|
|
40
|
-
*/
|
|
41
|
-
frameRole?: 'checkbox' | 'challenge' | 'unknown';
|
|
42
|
-
/** Outer solve-loop attempt this step belongs to. */
|
|
11
|
+
puzzleSource?: Vendor;
|
|
12
|
+
frameRole?: FrameRole;
|
|
43
13
|
attempt: number;
|
|
44
|
-
/** ms since solve() started. */
|
|
45
14
|
elapsedMs: number;
|
|
46
|
-
/** Free-form per-stage detail (action payload, clicked cells, etc.). */
|
|
47
15
|
meta?: Record<string, any>;
|
|
48
16
|
}
|
|
17
|
+
/** Tunables. Every field mirrors a `PageSolverConfig` field on the Python port; defaults are in SOLVE_DEFAULTS. */
|
|
49
18
|
export interface CaptchaKrakenConfig {
|
|
50
|
-
/**
|
|
51
|
-
* How the driver MOVES — a choice of input DEVICE, not a realism dial.
|
|
52
|
-
*
|
|
53
|
-
* - `'mouse'` (default) Bezier arcs, Fitts's-law durations, overshoot.
|
|
54
|
-
* - `'mobile'` touch events with finger kinematics. On a touch-only widget
|
|
55
|
-
* this is the difference between the page's handlers firing and
|
|
56
|
-
* not; a mousemove there is the wrong event, not a weaker one.
|
|
57
|
-
* Needs a Chromium-family page launched with `hasTouch: true`,
|
|
58
|
-
* or an Appium/WebdriverIO driver in {@link touchDriver}.
|
|
59
|
-
* - `'none'` the shortest legal path to the same DOM effect. Much faster,
|
|
60
|
-
* and detectable by anything scoring pointer telemetry — for
|
|
61
|
-
* fixtures, self-hosted targets, and stacks that humanise
|
|
62
|
-
* elsewhere.
|
|
63
|
-
*
|
|
64
|
-
* Unset falls back to `CAPTCHA_HUMANIZATION`, then `'mouse'`. Set
|
|
65
|
-
* {@link humanizer} instead to supply your own implementation.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```typescript
|
|
69
|
-
* const context = await browser.newContext({ ...devices['Pixel 7'], hasTouch: true });
|
|
70
|
-
* const solver = new CaptchaKrakenSolver({ humanization: 'mobile' });
|
|
71
|
-
* await solver.solve(await context.newPage());
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
19
|
+
/** Input device; default `mouse`. Falls back to CAPTCHA_HUMANIZATION. */
|
|
74
20
|
humanization?: HumanizationMode;
|
|
75
|
-
/**
|
|
76
|
-
* Your own {@link Humanizer}. Overrides {@link humanization} entirely — the
|
|
77
|
-
* driver then makes no decisions about pointer motion at all.
|
|
78
|
-
*
|
|
79
|
-
* Use it when you already model your users' input (a hardware pointer, a
|
|
80
|
-
* device farm, a recorded trace), or when the browser humanises for you:
|
|
81
|
-
* camoufox's `humanize` juggler re-humanises every `mouse.move()` it is
|
|
82
|
-
* handed, and composing both measured 82.1s against 13.4s on one solve.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```typescript
|
|
86
|
-
* import { NullHumanizer, type Humanizer } from 'captchakraken';
|
|
87
|
-
*
|
|
88
|
-
* class MyPointer extends NullHumanizer implements Humanizer {
|
|
89
|
-
* async move(page, to) { await myHardwareMouse.glideTo(to[0], to[1]); this.at = to; }
|
|
90
|
-
* }
|
|
91
|
-
* new CaptchaKrakenSolver({ humanizer: new MyPointer() });
|
|
92
|
-
* ```
|
|
93
|
-
*/
|
|
21
|
+
/** Your own gesture implementation; overrides `humanization`. */
|
|
94
22
|
humanizer?: Humanizer;
|
|
95
|
-
/**
|
|
96
|
-
* `humanization: 'mobile'` only. The thing that is actually TOUCHED, when it
|
|
97
|
-
* is not the page object — an Appium / WebdriverIO / Selenium driver on a real
|
|
98
|
-
* handset. Left unset, the mode dispatches CDP touch events at the page it
|
|
99
|
-
* was given, which is what browser mobile emulation wants.
|
|
100
|
-
*/
|
|
23
|
+
/** Mobile only: an Appium / WebdriverIO driver on a real handset. Unset dispatches CDP touch at the page. */
|
|
101
24
|
touchDriver?: any;
|
|
102
|
-
/**
|
|
103
|
-
* `humanization: 'mobile'` only. CSS-pixel → device-pixel transform for
|
|
104
|
-
* {@link touchDriver}. `scale` is usually `window.devicePixelRatio`; `origin`
|
|
105
|
-
* is the top-left of the webview in screen coordinates. The default identity
|
|
106
|
-
* is right for emulation and for callers who map coordinates themselves.
|
|
107
|
-
*/
|
|
25
|
+
/** Mobile only: CSS-pixel to device-pixel transform for `touchDriver`. */
|
|
108
26
|
touchTransform?: TouchTransform;
|
|
109
|
-
/**
|
|
110
|
-
* Optional observer fired at each intermediate solve stage. Receives a
|
|
111
|
-
* baseline screenshot before any action and one after every executed action.
|
|
112
|
-
* Use it to capture intermediate-stage screenshots, count steps, and time
|
|
113
|
-
* each phase. The callback may be async; the solver awaits it. Errors thrown
|
|
114
|
-
* by the callback are swallowed (never fail a solve because logging failed).
|
|
115
|
-
*
|
|
116
|
-
* The PNG at `event.screenshotPath` is owned by the callback once emitted —
|
|
117
|
-
* the solver will not delete it.
|
|
118
|
-
*/
|
|
27
|
+
/** Observer fired at each intermediate stage; errors are swallowed. */
|
|
119
28
|
onStep?: (event: SolveStepEvent) => void | Promise<void>;
|
|
120
|
-
/**
|
|
121
|
-
* Path to the bundled CaptchaKraken CLI root.
|
|
122
|
-
*
|
|
123
|
-
* Usually you do NOT need to set this. If omitted, the solver will auto-resolve the
|
|
124
|
-
* `python/` directory (the captchakraken package) shipped inside this npm package.
|
|
125
|
-
*/
|
|
29
|
+
/** Path to the bundled Python engine. Auto-resolved when omitted. */
|
|
126
30
|
repoPath?: string;
|
|
127
|
-
/**
|
|
128
|
-
* Command to run python (default: 'python' or 'python3').
|
|
129
|
-
*/
|
|
31
|
+
/** Python interpreter for the engine. Default: the bundled venv, else `python3`, else `python`. */
|
|
130
32
|
pythonCommand?: string;
|
|
131
|
-
/**
|
|
132
|
-
* vLLM LoRA name to invoke.
|
|
133
|
-
*
|
|
134
|
-
* Defaults to whatever `models.json` calls `latest` (via CAPTCHA_LORA_NAME if
|
|
135
|
-
* set) — NOT a literal. The name selects the PROMPT GENERATION as well as the
|
|
136
|
-
* weights, so a constant here would pin the prompts to one generation while
|
|
137
|
-
* `latest` moved on. Override only if you've registered a different module
|
|
138
|
-
* with the vLLM server; see model-name.ts.
|
|
139
|
-
*/
|
|
33
|
+
/** Served model name. Default: resolved from models.json for the endpoint in use. */
|
|
140
34
|
model?: string;
|
|
141
|
-
/**
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
* A routed model (Abyss) is four LoRA adapters behind one endpoint, and the
|
|
145
|
-
* router is the prompt family each request is about to send — so leaving this
|
|
146
|
-
* unset already reaches the right expert. This is the override: serve one
|
|
147
|
-
* arm, drive only the puzzles it owns, which is what a per-arm benchmark
|
|
148
|
-
* needs and what a licence holder pinning a single expert wants.
|
|
149
|
-
*
|
|
150
|
-
* Refused against a model that serves a single adapter — every model
|
|
151
|
-
* published so far — rather than ignored, because a run that quietly measured
|
|
152
|
-
* the generalist while reporting an expert is a number nobody can catch.
|
|
153
|
-
*
|
|
154
|
-
* Mirrors the Python `PageSolverConfig.expert`; also settable as
|
|
155
|
-
* CAPTCHA_EXPERT, which this forwards to the CLI ahead of.
|
|
156
|
-
*/
|
|
157
|
-
expert?: string;
|
|
158
|
-
/**
|
|
159
|
-
* Bearer token for the vLLM server (also picked up from VLLM_API_KEY env).
|
|
160
|
-
*/
|
|
35
|
+
/** Force one expert of a routed model. Also CAPTCHA_EXPERT. */
|
|
36
|
+
expert?: PromptFamily;
|
|
37
|
+
/** Bearer token; also read from CAPTCHA_KRAKEN_API_KEY / VLLM_API_KEY. */
|
|
161
38
|
apiKey?: string;
|
|
162
|
-
/**
|
|
163
|
-
* Starting mouse position (default: { x: 100, y: 100 }).
|
|
164
|
-
* HIGHLY RECOMMENDED to set this, prevents jumping around of the cursor when solving.
|
|
165
|
-
*/
|
|
39
|
+
/** Where the pointer starts. Default { x: 100, y: 100 }. */
|
|
166
40
|
startingMousePosition?: {
|
|
167
41
|
x: number;
|
|
168
42
|
y: number;
|
|
169
43
|
};
|
|
170
|
-
/**
|
|
171
|
-
* Automatically re-check for newly opened / next-step captchas after each solve
|
|
172
|
-
* attempt (e.g., clicking a checkbox opens an image challenge).
|
|
173
|
-
*
|
|
174
|
-
* The loop count that FITS inside overallSolveTimeoutMs, rather than one that
|
|
175
|
-
* needs policing by the clock: a round costs ~4-7s, so six rounds is the 45s
|
|
176
|
-
* budget. A backstop, not the normal way a hopeless solve ends — that is
|
|
177
|
-
* maxNoProgressRounds. Reaching this number is a bug report.
|
|
178
|
-
*
|
|
179
|
-
* Default: 6
|
|
180
|
-
*/
|
|
44
|
+
/** Solve rounds that fit the timeout. Default 6. */
|
|
181
45
|
maxSolveLoops?: number;
|
|
182
|
-
/**
|
|
183
|
-
* Consecutive rounds producing the SAME answer before the solve is abandoned.
|
|
184
|
-
*
|
|
185
|
-
* At temperature 0 the model is a function of the picture, so an identical
|
|
186
|
-
* answer means an identical picture — and every answer this driver produces is
|
|
187
|
-
* EXECUTED, so the previous one already ran and moved nothing. Repeating it
|
|
188
|
-
* cannot do better; it just spends a round.
|
|
189
|
-
*
|
|
190
|
-
* 2 rather than 1 because the first repeat also escalates to a RECORDING (see
|
|
191
|
-
* shouldRetryAsAnimated), which is the one recovery worth trying: a cycling
|
|
192
|
-
* board reads as a still and answers the same way every round. One more round
|
|
193
|
-
* buys that chance.
|
|
194
|
-
*
|
|
195
|
-
* Default: 2
|
|
196
|
-
*/
|
|
46
|
+
/** Consecutive identical answers before the solve is abandoned. Default 2. */
|
|
197
47
|
maxNoProgressRounds?: number;
|
|
198
|
-
/**
|
|
199
|
-
* Delay (ms) after executing actions before re-detecting captchas.
|
|
200
|
-
* Useful to allow challenge frames / new images to appear.
|
|
201
|
-
*
|
|
202
|
-
* Default: 1200
|
|
203
|
-
*/
|
|
48
|
+
/** Dwell after a round that answered nothing. Default 1200. */
|
|
204
49
|
postSolveDelayMs?: number;
|
|
205
|
-
/**
|
|
206
|
-
* Overall time limit (ms) for the entire solve loop.
|
|
207
|
-
*
|
|
208
|
-
* Default: 45000
|
|
209
|
-
*/
|
|
50
|
+
/** Whole-solve time limit. Default 45000. */
|
|
210
51
|
overallSolveTimeoutMs?: number;
|
|
211
|
-
/**
|
|
212
|
-
* Poll interval (ms) for the reCAPTCHA grid-cell-load wait — how often the
|
|
213
|
-
* solver re-screenshots the grid while waiting for tiles to stop fading in.
|
|
214
|
-
* Doubles as the inter-frame gap for the settle change-detector, so keep it
|
|
215
|
-
* comfortably above zero.
|
|
216
|
-
*
|
|
217
|
-
* Default: 250
|
|
218
|
-
*/
|
|
52
|
+
/** reCAPTCHA grid-load poll interval. Default 250. */
|
|
219
53
|
gridLoadPollIntervalMs?: number;
|
|
220
|
-
/**
|
|
221
|
-
* Overall timeout (ms) for the reCAPTCHA grid-cell-load wait. On timeout the
|
|
222
|
-
* solver proceeds to screenshot anyway (best-effort).
|
|
223
|
-
*
|
|
224
|
-
* Default: 8000
|
|
225
|
-
*/
|
|
54
|
+
/** reCAPTCHA grid-load timeout. Default 8000. */
|
|
226
55
|
gridLoadTimeoutMs?: number;
|
|
227
|
-
/**
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
*/
|
|
56
|
+
/** Board-paint gate poll interval. Default 180. */
|
|
57
|
+
boardPaintPollMs?: number;
|
|
58
|
+
/** How long to wait for a widget to paint its puzzle. Default 2500. */
|
|
59
|
+
boardPaintTimeoutMs?: number;
|
|
60
|
+
/** Share of the panel's centre that must carry structure. Default: the engine's own. */
|
|
61
|
+
boardPaintFloor?: number;
|
|
62
|
+
/** reCAPTCHA 3x3: how long to wait for a clicked tile to reload. Default 6000. */
|
|
235
63
|
recaptchaDynamicFadeWaitMs?: number;
|
|
236
|
-
/**
|
|
237
|
-
* reCAPTCHA 3x3 dynamic puzzles only. Minimum gap (ms) between the two frames
|
|
238
|
-
* the fade detectors diff, and the poll cadence for waitForAnyClickedTileLoaded
|
|
239
|
-
* / currentLoadingCells. Kept comfortably above zero so two consecutive frames
|
|
240
|
-
* during a slow fade differ enough for the change detector to fire (frames
|
|
241
|
-
* captured back-to-back can look identical mid-fade and read as "loaded").
|
|
242
|
-
*
|
|
243
|
-
* Default: 250
|
|
244
|
-
*/
|
|
64
|
+
/** reCAPTCHA 3x3: gap between the two frames the fade detectors diff. Default 250. */
|
|
245
65
|
recaptchaDynamicFadePollMs?: number;
|
|
246
|
-
/**
|
|
247
|
-
* reCAPTCHA 3x3 dynamic puzzles only. Grace window (ms) after clicking during
|
|
248
|
-
* which the solver watches the clicked tiles for the ONSET of a blank/fade
|
|
249
|
-
* before deciding the puzzle is solved. reCAPTCHA keeps a clicked tile
|
|
250
|
-
* SELECTED (showing its old image + a blue badge) for a couple of seconds and
|
|
251
|
-
* only THEN blanks it to swap in a replacement, so the window must comfortably
|
|
252
|
-
* exceed that delay or we submit while the refresh is still pending. If no
|
|
253
|
-
* clicked tile goes blank/changing within this window, the puzzle is treated
|
|
254
|
-
* as solved.
|
|
255
|
-
*
|
|
256
|
-
* Default: 4000
|
|
257
|
-
*/
|
|
66
|
+
/** reCAPTCHA 3x3: grace window after a click for the fade to begin. Default 4000. A window, not a snapshot: a clicked tile stays selected 1-3s and only then blanks. */
|
|
258
67
|
recaptchaFadeOnsetGraceMs?: number;
|
|
259
|
-
/**
|
|
260
|
-
* reCAPTCHA 3x3 dynamic puzzles only. Cap on the number of
|
|
261
|
-
* click → refresh → re-solve rounds within a single puzzle, independent of
|
|
262
|
-
* maxSolveLoops.
|
|
263
|
-
*
|
|
264
|
-
* Default: 8
|
|
265
|
-
*/
|
|
68
|
+
/** reCAPTCHA 3x3: cap on click/refresh rounds per puzzle. Default 8. */
|
|
266
69
|
recaptchaMaxDynamicRounds?: number;
|
|
267
|
-
/**
|
|
268
|
-
* reCAPTCHA 3x3 dynamic puzzles only. When true, the solver hovers the mouse
|
|
269
|
-
* over the just-clicked blank/fading tiles (in click order) while waiting for
|
|
270
|
-
* them to reload, mimicking a human. Disable to skip the hover behavior.
|
|
271
|
-
*
|
|
272
|
-
* Default: true
|
|
273
|
-
*/
|
|
70
|
+
/** reCAPTCHA 3x3: hover the reloading tiles while waiting. Default true. */
|
|
274
71
|
recaptchaTileHoverEnabled?: boolean;
|
|
275
|
-
/**
|
|
276
|
-
* While the model is generating a solution (the main idle window), drift the
|
|
277
|
-
* cursor over the challenge area with human-like trajectories instead of
|
|
278
|
-
* leaving it frozen. Cancelled the instant the model responds. Set false to
|
|
279
|
-
* keep the cursor still during inference.
|
|
280
|
-
*
|
|
281
|
-
* Default: true
|
|
282
|
-
*/
|
|
72
|
+
/** Drift the cursor over the challenge while the model thinks. Default true. */
|
|
283
73
|
idleMouseWander?: boolean;
|
|
284
|
-
/**
|
|
285
|
-
* After an action, the max time (ms) to watch for the solve outcome — the
|
|
286
|
-
* vendor's solved signal (checkbox checked / response token) or a freshly
|
|
287
|
-
* rendered next round — before falling back to a full re-detect. Returning as
|
|
288
|
-
* soon as the solved signal appears avoids re-entering the solve pipeline on a
|
|
289
|
-
* challenge frame that is merely animating closed.
|
|
290
|
-
*
|
|
291
|
-
* That is the window's whole job, and it is why a WRONG answer spends all of
|
|
292
|
-
* it: the vendors emit nothing on a wrong answer, they just re-deal. So
|
|
293
|
-
* sizing it means asking how late a real SUCCESS can arrive — measured over
|
|
294
|
-
* 34 successful rounds across 20 puzzle types on the fixture server: p50
|
|
295
|
-
* 360ms, max 528ms. 1000ms is that worst case plus headroom for a vendor
|
|
296
|
-
* round-trip nobody has measured yet. Down from 2500, which was spent in full
|
|
297
|
-
* on every unsuccessful round.
|
|
298
|
-
*
|
|
299
|
-
* Default: 1000
|
|
300
|
-
*/
|
|
74
|
+
/** How long to watch for the vendor's verdict after an action. Default 1000. */
|
|
301
75
|
postSolveOutcomeTimeoutMs?: number;
|
|
302
|
-
/**
|
|
303
|
-
* Poll interval (ms) inside that window.
|
|
304
|
-
*
|
|
305
|
-
* The success signals are polled, so detection latency is a MULTIPLE of this.
|
|
306
|
-
* Each poll is a couple of cheap DOM reads.
|
|
307
|
-
*
|
|
308
|
-
* Default: 75
|
|
309
|
-
*/
|
|
76
|
+
/** Poll interval inside that window. Default 75. */
|
|
310
77
|
postSolveOutcomePollMs?: number;
|
|
311
|
-
/**
|
|
312
|
-
* Guard against the captcha frame changing WHILE the model is generating.
|
|
313
|
-
* reCAPTCHA/hCaptcha fade fresh tiles in over ~1s; if new imagery paints
|
|
314
|
-
* between the screenshot we send the model and the moment its answer returns,
|
|
315
|
-
* that answer describes a stale ("undeveloped") frame and its tile picks /
|
|
316
|
-
* bboxes no longer line up with what's on screen. When enabled, the solver
|
|
317
|
-
* re-screenshots the frame after inference, and if it changed it discards the
|
|
318
|
-
* stale answer and re-solves on the fresh frame (see maxStaleFrameReSolves).
|
|
319
|
-
*
|
|
320
|
-
* Default: true
|
|
321
|
-
*/
|
|
78
|
+
/** Re-solve when the frame changed during inference. Default true. */
|
|
322
79
|
staleFrameReSolveEnabled?: boolean;
|
|
323
|
-
/**
|
|
324
|
-
* Fraction of pixels (0–1) that must differ between the frame sent to the
|
|
325
|
-
* model and a screenshot taken right after inference for the frame to count
|
|
326
|
-
* as "changed during inference" (a tile faded in / refreshed). Below this,
|
|
327
|
-
* the frame is treated as unchanged and the answer is used as-is. Reuses the
|
|
328
|
-
* same frame-diff primitive as the reCAPTCHA settle detector.
|
|
329
|
-
*
|
|
330
|
-
* Default: 0.02
|
|
331
|
-
*/
|
|
80
|
+
/** Fraction of pixels that must differ to count as changed. Default 0.02. */
|
|
332
81
|
staleFrameDiffThreshold?: number;
|
|
333
|
-
/**
|
|
334
|
-
* Max number of times to re-screenshot + re-solve when the frame keeps
|
|
335
|
-
* changing during inference, before giving up and acting on the latest answer
|
|
336
|
-
* (better to act than to spin). Set 0 to detect-and-log without re-solving.
|
|
337
|
-
*
|
|
338
|
-
* Default: 2
|
|
339
|
-
*/
|
|
82
|
+
/** Re-solves allowed per round. Default 2. */
|
|
340
83
|
maxStaleFrameReSolves?: number;
|
|
341
|
-
/**
|
|
342
|
-
* Fraction of pixels (0–1) that must differ between two consecutive challenge
|
|
343
|
-
* frames for it to still count as "moving" (loading/animating) rather than
|
|
344
|
-
* settled.
|
|
345
|
-
*
|
|
346
|
-
* Default: 0.01
|
|
347
|
-
*/
|
|
84
|
+
/** Settle monitor: fraction of pixels that still count as moving. Default 0.01. */
|
|
348
85
|
settleDiffThreshold?: number;
|
|
349
|
-
/**
|
|
350
|
-
* How long to wait for hCaptcha's task images to paint before screenshotting
|
|
351
|
-
* anyway. Best-effort by design — the screenshot happens either way — so it
|
|
352
|
-
* is bounded by what a loading tile plausibly costs. Default: 3000
|
|
353
|
-
*/
|
|
86
|
+
/** How long to wait for hCaptcha's task images to paint. Default 3000. */
|
|
354
87
|
hcaptchaImagesTimeoutMs?: number;
|
|
355
|
-
/**
|
|
88
|
+
/** Settle monitor poll interval. Default 220. */
|
|
356
89
|
settlePollMs?: number;
|
|
357
|
-
/**
|
|
358
|
-
* Consecutive still frame-pairs required before the challenge is declared
|
|
359
|
-
* settled and safe to screenshot for the model.
|
|
360
|
-
*
|
|
361
|
-
* Default: 2
|
|
362
|
-
*/
|
|
90
|
+
/** Consecutive still frame pairs that mean settled. Default 2. */
|
|
363
91
|
settleFrames?: number;
|
|
364
|
-
/**
|
|
92
|
+
/** Settle monitor timeout. Default 9000. */
|
|
365
93
|
settleTimeoutMs?: number;
|
|
366
|
-
/**
|
|
367
|
-
* If the challenge keeps changing continuously for at least this long without
|
|
368
|
-
* ever settling, it's treated as an **animated / video** challenge (surfaced
|
|
369
|
-
* distinctly, not as "unsupported"). Static grids settle in ~1–2s; a video
|
|
370
|
-
* never does.
|
|
371
|
-
*
|
|
372
|
-
* Default: 4500
|
|
373
|
-
*/
|
|
94
|
+
/** Still moving after this long means animated. Default 4500. */
|
|
374
95
|
animatedChallengeAfterMs?: number;
|
|
375
|
-
/**
|
|
376
|
-
* Record and solve animated challenges. When false, a challenge that never
|
|
377
|
-
* settles fails with `.animated = true` as it did before — for callers who
|
|
378
|
-
* would rather fail fast than spend the recording time.
|
|
379
|
-
*
|
|
380
|
-
* Default: true
|
|
381
|
-
*/
|
|
96
|
+
/** Record and solve animated challenges from keyframes. Default true. */
|
|
382
97
|
videoSolveEnabled?: boolean;
|
|
383
|
-
/**
|
|
384
|
-
* Length (ms) and frame rate of the burst recorded from an animated challenge.
|
|
385
|
-
*
|
|
386
|
-
* Do not tune these casually. They are deliberately identical to the geometry
|
|
387
|
-
* the training corpus was collected at (4000ms @ 10fps), and the keyframe
|
|
388
|
-
* slicer reads the clip's temporal structure — a different length or rate
|
|
389
|
-
* changes where a cycle's period lands across the frames, so the live set gets
|
|
390
|
-
* sliced differently from the trained set and the model's frame number stops
|
|
391
|
-
* meaning what the driver thinks it means.
|
|
392
|
-
*
|
|
393
|
-
* Defaults: 4000 / 10
|
|
394
|
-
*/
|
|
98
|
+
/** Burst floor. Default 4000; matches the training corpus. */
|
|
395
99
|
videoBurstDurationMs?: number;
|
|
100
|
+
/** Burst frame rate. Default 10; matches the training corpus. */
|
|
396
101
|
videoBurstFps?: number;
|
|
397
|
-
/**
|
|
398
|
-
* How long (ms) to wait for the widget to return to the keyframe the model
|
|
399
|
-
* chose, before clicking anyway; and the poll interval while waiting.
|
|
400
|
-
*
|
|
401
|
-
* Bounded because the alternative is worse: these puzzles cycle, so the state
|
|
402
|
-
* does come back — but if the recording caught a one-off transition it never
|
|
403
|
-
* will, and a click on the model's coordinates is a better use of the
|
|
404
|
-
* remaining budget than a timeout.
|
|
405
|
-
*
|
|
406
|
-
* Defaults: 6000 / 120
|
|
407
|
-
*/
|
|
102
|
+
/** How long to hold a click waiting for the chosen keyframe. Default 9000: GeeTest svg dwells up to 2.7s a screen, so a 3-screen cycle is 8.1s worst case and 6s gave up one screen short. */
|
|
408
103
|
keyframeWaitTimeoutMs?: number;
|
|
104
|
+
/** Poll interval while waiting. Default 120. */
|
|
409
105
|
keyframeWaitPollMs?: number;
|
|
410
|
-
/**
|
|
411
|
-
* Hard ceiling on a burst that never repeats a screen. The burst normally
|
|
412
|
-
* ends when the cycle closes; this bounds a continuous animation, which never
|
|
413
|
-
* closes one. Default 12000ms.
|
|
414
|
-
*/
|
|
106
|
+
/** Burst ceiling for a board that never repeats a screen. Default 12000. */
|
|
415
107
|
videoBurstMaxMs?: number;
|
|
416
|
-
/**
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
* shape, which learned the same thing two rounds later.
|
|
420
|
-
*/
|
|
108
|
+
/** Consecutive moved settle polls that mean animated. Default 5. */
|
|
109
|
+
animatedMotionStreak?: number;
|
|
110
|
+
/** Record while the still is being read, so a cycling board is known before its answer runs. Default true. */
|
|
421
111
|
speculativeBurstEnabled?: boolean;
|
|
422
|
-
/**
|
|
423
|
-
* Extra wall clock (ms) granted ONCE, the first time a solve escalates to a
|
|
424
|
-
* recording. NOT a looser `overallSolveTimeoutMs`.
|
|
425
|
-
*
|
|
426
|
-
* That budget counts ROUNDS — `maxSolveLoops` x ~7000ms, which
|
|
427
|
-
* no-progress.test.ts pins. A recording is not a round: it is a fixed extra
|
|
428
|
-
* stage costing the burst, the slice, one MULTI-IMAGE inference (six
|
|
429
|
-
* keyframes, several times a still's) and the wait for the widget to come
|
|
430
|
-
* back round to the chosen frame. Nothing in the 45s was set aside for it, so
|
|
431
|
-
* an escalation late in a solve simply ran the clock out and reported a
|
|
432
|
-
* timeout — which reads as a slow model rather than as a budget with no room
|
|
433
|
-
* for what the solver had just decided to do.
|
|
434
|
-
*
|
|
435
|
-
* Measured 2026-08-22, a full driver-gate run: an hCaptcha click-by-trait board,
|
|
436
|
-
* an hCaptcha connect-the-path board and an hCaptcha 3x3 property grid each failed this port
|
|
437
|
-
* with "Captcha solve timed out after 45000ms (attempt 6/6)" at 49-59s, and
|
|
438
|
-
* solve in 14-20s on the rounds the still path answers them.
|
|
439
|
-
*
|
|
440
|
-
* The total granted is `videoBurstDurationMs + keyframeWaitTimeoutMs + this`,
|
|
441
|
-
* derived so a longer burst carries its own budget — page_solver.py's
|
|
442
|
-
* `video_budget_ms` is the same arithmetic, since the two ports must not
|
|
443
|
-
* disagree about how long a video solve may take.
|
|
444
|
-
*
|
|
445
|
-
* Granted only when `videoSolveEnabled`: a caller who wants a hard deadline
|
|
446
|
-
* turns recording off, which already means "fail fast rather than spend the
|
|
447
|
-
* recording time".
|
|
448
|
-
*
|
|
449
|
-
* Default: 8000
|
|
450
|
-
*/
|
|
112
|
+
/** Extra wall clock granted once when a solve escalates to a recording. Default 8000. */
|
|
451
113
|
videoExtraInferenceMs?: number;
|
|
452
|
-
/**
|
|
453
|
-
* After clicking Submit/Verify, the solver EXPECTS the frame to change (advance
|
|
454
|
-
* to the next round, or close because it was accepted). This is how long (ms)
|
|
455
|
-
* to wait for that transition to begin before re-evaluating — so the shift
|
|
456
|
-
* itself is never screenshotted and mis-read as a fresh (blank) puzzle.
|
|
457
|
-
*
|
|
458
|
-
* Default: 4000
|
|
459
|
-
*/
|
|
114
|
+
/** After a submit, how long to wait for the frame to change. Default 4000. */
|
|
460
115
|
postSubmitChangeTimeoutMs?: number;
|
|
461
|
-
/**
|
|
462
|
-
* Per-call timeout (ms) for element screenshots of the challenge iframe.
|
|
463
|
-
* Playwright's default is 30000, which means a stale/transitioning handle
|
|
464
|
-
* (e.g. hCaptcha swapped in the next round while we held the old iframe)
|
|
465
|
-
* hangs the whole solve for 30s before failing. Bounding it lets a stale
|
|
466
|
-
* handle fail fast so the solve loop can re-detect the fresh challenge.
|
|
467
|
-
*
|
|
468
|
-
* Default: 8000
|
|
469
|
-
*/
|
|
116
|
+
/** Per-call timeout for element screenshots. Default 8000. */
|
|
470
117
|
elementScreenshotTimeoutMs?: number;
|
|
471
|
-
/**
|
|
472
|
-
* How long an `onStep` observer's screenshot may take, ms (default 2000).
|
|
473
|
-
*
|
|
474
|
-
* Deliberately separate from `elementScreenshotTimeoutMs`, which is sized for
|
|
475
|
-
* the picture the MODEL reads. This one is a trace snapshot: it is taken with
|
|
476
|
-
* `animations: 'disabled'`, which makes Playwright wait for the element to
|
|
477
|
-
* stop moving first, and on a still-animating widget that ran to the full 8s
|
|
478
|
-
* on every step — 8.0s of a measured 12.0s solve. An observer must never cost
|
|
479
|
-
* more than the action it observes, and a missed frame in a trace costs
|
|
480
|
-
* nothing. Ignored entirely when no `onStep` is set.
|
|
481
|
-
*/
|
|
118
|
+
/** Timeout for an `onStep` screenshot. Default 2000. */
|
|
482
119
|
stepScreenshotTimeoutMs?: number;
|
|
483
|
-
/**
|
|
484
|
-
* After a submit, hCaptcha may replace the challenge iframe for the next
|
|
485
|
-
* round, detaching the handle we just detected ("element is not visible").
|
|
486
|
-
* This is a transition, not a dead puzzle: how many times to back off,
|
|
487
|
-
* re-detect the fresh challenge, and retry before giving up.
|
|
488
|
-
*
|
|
489
|
-
* Default: 3
|
|
490
|
-
*/
|
|
120
|
+
/** Re-detect retries after a stale handle. Default 3. */
|
|
491
121
|
maxStaleElementRetries?: number;
|
|
492
|
-
/**
|
|
493
|
-
* Backoff (ms) before re-detecting after a stale-element screenshot failure,
|
|
494
|
-
* giving the round transition time to finish.
|
|
495
|
-
*
|
|
496
|
-
* Default: 900
|
|
497
|
-
*/
|
|
122
|
+
/** Backoff before re-detecting. Default 900. */
|
|
498
123
|
staleElementBackoffMs?: number;
|
|
499
|
-
/**
|
|
500
|
-
* When the model reports "unsupported" *after we've already interacted* (i.e.
|
|
501
|
-
* mid multi-round), it's almost always a not-yet-settled next round rather
|
|
502
|
-
* than a genuinely unsupported puzzle. This is how many times to wait for the
|
|
503
|
-
* challenge to settle and re-solve before giving up. (A single retry loses a
|
|
504
|
-
* race when the next round loads slowly.)
|
|
505
|
-
*
|
|
506
|
-
* Default: 3
|
|
507
|
-
*/
|
|
124
|
+
/** Retries when "unsupported" arrives mid-solve on a transitional frame. Default 3. */
|
|
508
125
|
maxUnsupportedReSolves?: number;
|
|
509
126
|
}
|
|
510
127
|
export interface BoundingBox {
|
|
@@ -513,61 +130,34 @@ export interface BoundingBox {
|
|
|
513
130
|
2: number;
|
|
514
131
|
3: number;
|
|
515
132
|
}
|
|
516
|
-
/**
|
|
517
|
-
* Fields present ONLY on actions from an animated challenge.
|
|
518
|
-
*
|
|
519
|
-
* Why an action carries a picture: on an animated puzzle the target is visible
|
|
520
|
-
* only part of the time, so the coordinates are correct only while the widget
|
|
521
|
-
* looks the way it did in that keyframe. The driver holds the mouse until the
|
|
522
|
-
* live neighbourhood around the click point matches the same neighbourhood of
|
|
523
|
-
* `await_keyframe`, then clicks. Without the wait, a click on a fading sprite
|
|
524
|
-
* lands on background.
|
|
525
|
-
*
|
|
526
|
-
* Both are set together or not at all — a number with no image cannot be waited
|
|
527
|
-
* on, and an image with no number cannot be reported. Absent on every still
|
|
528
|
-
* puzzle, where there is no moment to wait for.
|
|
529
|
-
*/
|
|
133
|
+
/** Present only on actions from an animated challenge: the keyframe the answer was read off. */
|
|
530
134
|
export interface AnimatedActionFields {
|
|
531
|
-
/** Absolute path to the keyframe the model chose to act on. */
|
|
532
135
|
await_keyframe?: string | null;
|
|
533
|
-
/** Its 1-based number in the keyframe set that was sent. */
|
|
534
136
|
frame?: number | null;
|
|
535
137
|
}
|
|
536
138
|
export interface ClickAction extends AnimatedActionFields {
|
|
537
|
-
action:
|
|
538
|
-
/**
|
|
539
|
-
* One or more normalized [x1, y1, x2, y2] bboxes (0–1 fractions of the
|
|
540
|
-
* screenshot). Each entry produces one click. Emitted by v2 CLI for both
|
|
541
|
-
* grid selections and click-puzzle points.
|
|
542
|
-
*/
|
|
139
|
+
action: typeof ActionKind.CLICK;
|
|
140
|
+
/** Normalised [x1, y1, x2, y2] boxes, one click each. */
|
|
543
141
|
target_bounding_boxes?: Array<[number, number, number, number]>;
|
|
544
|
-
/** Legacy v1 fields kept for backwards-compat with older CLI builds. */
|
|
545
142
|
target_number?: number | null;
|
|
546
143
|
target_bounding_box?: [number, number, number, number] | null;
|
|
547
144
|
target_coordinates?: [number, number] | null;
|
|
548
145
|
}
|
|
549
146
|
export interface DragAction extends AnimatedActionFields {
|
|
550
|
-
action:
|
|
551
|
-
/**
|
|
552
|
-
* Null on a PUZZLE-PIECE SLIDER. Not a missing field — the shape of the
|
|
553
|
-
* answer: what you grab (a handle, elsewhere on the widget) is not what has
|
|
554
|
-
* to arrive (the piece), and how far one moves the other is a vendor detail
|
|
555
|
-
* no picture reveals. The driver reads a null source as "find the slider and
|
|
556
|
-
* close the loop on the piece" (see executeSlide).
|
|
557
|
-
*/
|
|
147
|
+
action: typeof ActionKind.DRAG;
|
|
148
|
+
/** Null on a puzzle-piece slider: the driver finds the handle and closes the loop on the piece. */
|
|
558
149
|
source_bounding_box: [number, number, number, number] | null;
|
|
559
150
|
target_bounding_box: [number, number, number, number];
|
|
560
151
|
}
|
|
561
|
-
/** Distorted-text captchas: the answer is a string, not a place. */
|
|
562
152
|
export interface TypeAction {
|
|
563
|
-
action:
|
|
153
|
+
action: typeof ActionKind.TYPE;
|
|
564
154
|
text: string;
|
|
565
155
|
}
|
|
566
156
|
export interface DoneAction {
|
|
567
|
-
action:
|
|
157
|
+
action: typeof ActionKind.DONE;
|
|
568
158
|
}
|
|
569
159
|
export interface WaitAction {
|
|
570
|
-
action:
|
|
160
|
+
action: typeof ActionKind.WAIT;
|
|
571
161
|
duration_ms: number;
|
|
572
162
|
}
|
|
573
163
|
export type CaptchaAction = ClickAction | WaitAction | DragAction | TypeAction | DoneAction;
|
|
@@ -596,14 +186,6 @@ export interface SolveResult {
|
|
|
596
186
|
cachedInputTokens: number;
|
|
597
187
|
estimatedCost: number;
|
|
598
188
|
};
|
|
599
|
-
/**
|
|
600
|
-
* Where this solve's wall-clock went, in milliseconds per phase — the same
|
|
601
|
-
* partition `CAPTCHA_TIMINGS=1` prints, handed to the caller instead of to
|
|
602
|
-
* stderr. Mirrors `SolveResult.phases` on the Python port.
|
|
603
|
-
*
|
|
604
|
-
* Returned rather than only logged because "the solve took 12s" is not
|
|
605
|
-
* actionable and "the settle monitor spent 4s of it" is, and a caller
|
|
606
|
-
* measuring a fleet cannot scrape another process's stderr.
|
|
607
|
-
*/
|
|
189
|
+
/** Milliseconds per phase, plus `total`. */
|
|
608
190
|
phases?: Record<string, number>;
|
|
609
191
|
}
|