granclaw 0.0.1-beta.37 → 0.0.1-beta.38

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.
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * captcha-detect.ts
4
+ *
5
+ * A stringified IIFE we ship to `agent-browser eval` to decide whether the
6
+ * currently-open page is blocked behind some kind of bot/captcha wall. The
7
+ * runner polls this snippet after every navigation command so CapMonster and
8
+ * the stealth extension have a chance to clear the challenge before the
9
+ * agent starts reading the page.
10
+ *
11
+ * It covers:
12
+ * - Cloudflare JS interstitials ("Just a moment...", "Checking your browser…",
13
+ * "Attention Required"), including the common localised titles.
14
+ * - Cloudflare managed-challenge DOM markers (#challenge-stage,
15
+ * #challenge-running, .cf-browser-verification, .cf-im-under-attack).
16
+ * - The classic widget iframes: reCaptcha, hCaptcha, Cloudflare Turnstile,
17
+ * DataDome, Arkose/FunCaptcha.
18
+ * - Inline widgets: .g-recaptcha, .h-captcha, [class*="captcha"].
19
+ * - PerimeterX / HUMAN bot check: #px-captcha.
20
+ *
21
+ * IMPORTANT: this runs as a string inside Chrome, NOT in Node. No ES features
22
+ * newer than the detector's tested Chrome baseline, no TypeScript. Keep it
23
+ * ES5-flavoured so it stays safe if the agent-browser build ever targets an
24
+ * older chromium.
25
+ */
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.CAPTCHA_DETECT_JS = void 0;
28
+ exports.CAPTCHA_DETECT_JS = `(function() {
29
+ var title = (document.title || '').toLowerCase();
30
+ var titlePatterns = [
31
+ 'just a moment', // Cloudflare interstitial (EN)
32
+ 'un momento', // CF Spanish
33
+ 'un instant', // CF French
34
+ 'einen moment', // CF German
35
+ 'checking your browser', // CF older / generic anti-bot
36
+ 'attention required', // CF WAF block page
37
+ 'please wait', // generic "please wait while we check"
38
+ ];
39
+ for (var i = 0; i < titlePatterns.length; i++) {
40
+ if (title.indexOf(titlePatterns[i]) !== -1) return 'captcha';
41
+ }
42
+
43
+ var selectors = [
44
+ // Widget iframes
45
+ 'iframe[src*="captcha-delivery"]', // DataDome
46
+ 'iframe[src*="geo.captcha"]', // DataDome geo
47
+ 'iframe[src*="recaptcha"]', // reCaptcha v2/v3
48
+ 'iframe[src*="hcaptcha"]', // hCaptcha
49
+ 'iframe[src*="challenges.cloudflare"]', // Cloudflare Turnstile
50
+ 'iframe[src*="arkoselabs"]', // Arkose FunCaptcha
51
+ 'iframe[src*="funcaptcha"]',
52
+ // Inline widgets
53
+ '.g-recaptcha',
54
+ '.h-captcha',
55
+ '[class*="captcha"]',
56
+ // Cloudflare interstitial / managed challenge
57
+ '#challenge-stage',
58
+ '#challenge-running',
59
+ '#cf-challenge-running',
60
+ '.cf-browser-verification',
61
+ '.cf-im-under-attack',
62
+ // PerimeterX / HUMAN
63
+ '#px-captcha',
64
+ ];
65
+ for (var j = 0; j < selectors.length; j++) {
66
+ if (document.querySelector(selectors[j])) return 'captcha';
67
+ }
68
+ return 'clear';
69
+ })()`;
@@ -51,6 +51,7 @@ const schedules_db_js_1 = require("../schedules-db.js");
51
51
  const cron_parser_1 = require("cron-parser");
52
52
  const session_manager_js_1 = require("../browser/session-manager.js");
53
53
  const stealth_js_1 = require("../browser/stealth.js");
54
+ const captcha_detect_js_1 = require("./captcha-detect.js");
54
55
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
55
56
  /**
56
57
  * Resolve the templates directory.
@@ -683,27 +684,19 @@ async function runAgent(agent, message, onChunk, options) {
683
684
  // Privileged commands (record, close, tab close for session 0,
684
685
  // session management) are rejected — the runtime owns those.
685
686
  /**
686
- * After a navigation command, check if a CAPTCHA is blocking the page.
687
- * Waits up to 30s for the CapMonster extension to auto-solve it.
687
+ * After a navigation command, check whether the page is blocked by any
688
+ * bot/captcha wall (widget captcha OR Cloudflare-style JS interstitial)
689
+ * and poll until CapMonster + the stealth extension have cleared it.
690
+ *
691
+ * Budget is 45s — Cloudflare's "Just a moment..." managed challenge often
692
+ * takes 15–20s on a cold datacenter IP, and CapMonster-solved widgets can
693
+ * take another pass, so shorter deadlines were causing real unblocks to
694
+ * be reported as "unsolved" (regression verified on mariados 2026-04-15).
695
+ *
688
696
  * Returns 'clear' (no captcha / solved), 'unsolved' (still blocked after timeout).
689
697
  */
690
698
  async function waitForCaptchaResolution(bin, sessionId) {
691
- const CAPTCHA_JS = `(function() {
692
- var patterns = [
693
- 'iframe[src*="captcha-delivery"]',
694
- 'iframe[src*="geo.captcha"]',
695
- 'iframe[src*="recaptcha"]',
696
- 'iframe[src*="hcaptcha"]',
697
- 'iframe[src*="challenges.cloudflare"]',
698
- '.g-recaptcha',
699
- '.h-captcha',
700
- '[class*="captcha"]',
701
- ];
702
- return patterns.some(function(s) {
703
- return !!document.querySelector(s);
704
- }) ? 'captcha' : 'clear';
705
- })()`;
706
- const evalArgv = (id) => ['--session', id, 'eval', CAPTCHA_JS];
699
+ const evalArgv = (id) => ['--session', id, 'eval', captcha_detect_js_1.CAPTCHA_DETECT_JS];
707
700
  const check = async () => {
708
701
  try {
709
702
  const { stdout } = await execFileAsync(bin, evalArgv(sessionId), { timeout: 8_000 });
@@ -717,8 +710,8 @@ async function runAgent(agent, message, onChunk, options) {
717
710
  await new Promise(r => setTimeout(r, 1500));
718
711
  if (!await check())
719
712
  return 'clear';
720
- // CAPTCHA detected — wait up to 30s for auto-solve
721
- const deadline = Date.now() + 30_000;
713
+ // Challenge detected — poll every 2s until the page clears or 45s elapses.
714
+ const deadline = Date.now() + 45_000;
722
715
  while (Date.now() < deadline) {
723
716
  await new Promise(r => setTimeout(r, 2_000));
724
717
  if (!await check())
@@ -756,7 +749,7 @@ async function runAgent(agent, message, onChunk, options) {
756
749
  'Do not call `record start`, `record stop`, `close`, or `session` — the runtime manages those.',
757
750
  'You do not need to screenshot for audit — the whole session is recorded as video automatically.',
758
751
  'Saved logins persist automatically when the user has set up a profile via the dashboard Browser view.',
759
- 'CAPTCHA handling: if a page returns a CAPTCHA, waitthe browser has an automatic solver extension. If it is not resolved after ~30 seconds, use request_human_browser_takeover to let the user solve it.',
752
+ 'CAPTCHA & Cloudflare handling: the browser ships an automatic CapMonster extension plus a stealth extension. After `open` or `reload` the runtime automatically polls for up to ~45 seconds while any captcha widget OR Cloudflare JS interstitial ("Just a moment...", "Checking your browser…", "Attention Required") clears do NOT bail out early, do NOT call request_human_browser_takeover just because the first snapshot shows the interstitial. If, after your tool call returns, the response still contains the "CAPTCHA detected and not auto-solved" warning, THEN call request_human_browser_takeover.',
760
753
  'Examples: {"command":"open","args":["https://example.com"]}, {"command":"click","args":["--ref","e12"]}, {"command":"fill","args":["--ref","e5","Alice"]}',
761
754
  ],
762
755
  parameters: {
@@ -831,7 +824,7 @@ async function runAgent(agent, message, onChunk, options) {
831
824
  return {
832
825
  content: [{
833
826
  type: 'text',
834
- text: out + '\n\n⚠️ CAPTCHA detected and not auto-solved after 30s. ' +
827
+ text: out + '\n\n⚠️ CAPTCHA detected and not auto-solved after 45s. ' +
835
828
  'Use request_human_browser_takeover to let the user solve it, or try a different URL.',
836
829
  }],
837
830
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "granclaw",
3
- "version": "0.0.1-beta.37",
3
+ "version": "0.0.1-beta.38",
4
4
  "description": "A personal AI assistant you run on your own machine.",
5
5
  "license": "MIT",
6
6
  "repository": {