granclaw 0.0.1-beta.55 → 0.0.1-beta.56

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.
Files changed (65) hide show
  1. package/dist/backend/agent/captcha-detect.js +33 -31
  2. package/dist/backend/agent/runner-pi.js +32 -69
  3. package/dist/backend/browser/stealth.js +0 -65
  4. package/dist/frontend/assets/{index-aOmMmTYZ.js → index-BFJ0hsuN.js} +1 -1
  5. package/dist/frontend/index.html +1 -1
  6. package/package.json +1 -1
  7. package/templates/AGENT.onboarding.md +2 -2
  8. package/templates/SYSTEM.md +1 -1
  9. package/dist/backend/assets/capmonster-extension/_locales/en/messages.json +0 -226
  10. package/dist/backend/assets/capmonster-extension/_locales/ru/messages.json +0 -226
  11. package/dist/backend/assets/capmonster-extension/_metadata/verified_contents.json +0 -1
  12. package/dist/backend/assets/capmonster-extension/background.js +0 -2
  13. package/dist/backend/assets/capmonster-extension/background.js.LICENSE.txt +0 -29
  14. package/dist/backend/assets/capmonster-extension/binanceInterceptor.js +0 -1
  15. package/dist/backend/assets/capmonster-extension/blsInterceptor.js +0 -2
  16. package/dist/backend/assets/capmonster-extension/blsInterceptor.js.LICENSE.txt +0 -29
  17. package/dist/backend/assets/capmonster-extension/content.js +0 -2
  18. package/dist/backend/assets/capmonster-extension/content.js.LICENSE.txt +0 -29
  19. package/dist/backend/assets/capmonster-extension/css/antd.variable.min.css +0 -10
  20. package/dist/backend/assets/capmonster-extension/css/content/solver.css +0 -152
  21. package/dist/backend/assets/capmonster-extension/css/popup/styles.css +0 -110
  22. package/dist/backend/assets/capmonster-extension/defaultSettings.json +0 -62
  23. package/dist/backend/assets/capmonster-extension/devtools/devtools.html +0 -1
  24. package/dist/backend/assets/capmonster-extension/devtools/devtools.js +0 -1
  25. package/dist/backend/assets/capmonster-extension/devtools/panel.html +0 -11
  26. package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Bold.ttf +0 -0
  27. package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Medium.ttf +0 -0
  28. package/dist/backend/assets/capmonster-extension/fonts/roboto/Roboto-Regular.ttf +0 -0
  29. package/dist/backend/assets/capmonster-extension/fonts/ubuntu/Ubuntu-B.ttf +0 -0
  30. package/dist/backend/assets/capmonster-extension/geetestInterceptor.js +0 -1
  31. package/dist/backend/assets/capmonster-extension/hcaptcha.js +0 -2
  32. package/dist/backend/assets/capmonster-extension/hcaptcha.js.LICENSE.txt +0 -29
  33. package/dist/backend/assets/capmonster-extension/hcaptchaInterceptor.js +0 -1
  34. package/dist/backend/assets/capmonster-extension/img/20x20_binance.svg +0 -14
  35. package/dist/backend/assets/capmonster-extension/img/20x20_bls.svg +0 -9
  36. package/dist/backend/assets/capmonster-extension/img/20x20_geetest.svg +0 -6
  37. package/dist/backend/assets/capmonster-extension/img/20x20_hcaptcha.svg +0 -28
  38. package/dist/backend/assets/capmonster-extension/img/20x20_recaptcha.svg +0 -5
  39. package/dist/backend/assets/capmonster-extension/img/20x20_text_captcha.svg +0 -5
  40. package/dist/backend/assets/capmonster-extension/img/20x20_turnstile.svg +0 -11
  41. package/dist/backend/assets/capmonster-extension/img/blue-cogs-animated.gif +0 -0
  42. package/dist/backend/assets/capmonster-extension/img/green-cogs.png +0 -0
  43. package/dist/backend/assets/capmonster-extension/img/icon.png +0 -0
  44. package/dist/backend/assets/capmonster-extension/img/logo.svg +0 -20
  45. package/dist/backend/assets/capmonster-extension/img/logo_icon.png +0 -0
  46. package/dist/backend/assets/capmonster-extension/img/red-cogs.png +0 -0
  47. package/dist/backend/assets/capmonster-extension/img/white-cogs.png +0 -0
  48. package/dist/backend/assets/capmonster-extension/manifest.json +0 -107
  49. package/dist/backend/assets/capmonster-extension/manifest_chrome.json +0 -105
  50. package/dist/backend/assets/capmonster-extension/manifest_firefox.json +0 -120
  51. package/dist/backend/assets/capmonster-extension/pageScript.js +0 -1
  52. package/dist/backend/assets/capmonster-extension/pageScriptHandler.js +0 -2
  53. package/dist/backend/assets/capmonster-extension/pageScriptHandler.js.LICENSE.txt +0 -14
  54. package/dist/backend/assets/capmonster-extension/panel.js +0 -16
  55. package/dist/backend/assets/capmonster-extension/panel.js.LICENSE.txt +0 -90
  56. package/dist/backend/assets/capmonster-extension/polyfills/browser-polyfill.js +0 -1
  57. package/dist/backend/assets/capmonster-extension/popup.html +0 -13
  58. package/dist/backend/assets/capmonster-extension/popup.js +0 -222
  59. package/dist/backend/assets/capmonster-extension/popup.js.LICENSE.txt +0 -90
  60. package/dist/backend/assets/capmonster-extension/recaptcha.js +0 -2
  61. package/dist/backend/assets/capmonster-extension/recaptcha.js.LICENSE.txt +0 -29
  62. package/dist/backend/assets/capmonster-extension/recaptcha2Interceptor.js +0 -1
  63. package/dist/backend/assets/capmonster-extension/recaptcha3Interceptor.js +0 -1
  64. package/dist/backend/assets/capmonster-extension/turnstileInterceptor.js +0 -2
  65. package/dist/backend/assets/capmonster-extension/turnstileInterceptor.js.LICENSE.txt +0 -14
@@ -4,22 +4,19 @@
4
4
  *
5
5
  * A stringified IIFE we ship to `agent-browser eval` to decide whether the
6
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.
7
+ * runner polls this snippet after every navigation command so the stealth
8
+ * extension has a chance to clear Cloudflare interstitials before the agent
9
+ * starts reading the page.
10
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.
11
+ * Returns one of three values:
12
+ * 'interstitial' Cloudflare JS challenge ("Just a moment..."). The stealth
13
+ * extension can clear these; the runner should poll and wait.
14
+ * 'captcha' — An actual captcha widget (reCAPTCHA, hCaptcha, Turnstile,
15
+ * etc.) that requires human intervention. The runner should
16
+ * request a takeover immediately.
17
+ * 'clear' — No challenge detected.
20
18
  *
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
19
+ * IMPORTANT: this runs as a string inside Chrome, NOT in Node. Keep it
23
20
  * ES5-flavoured so it stays safe if the agent-browser build ever targets an
24
21
  * older chromium.
25
22
  */
@@ -27,21 +24,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
27
24
  exports.CAPTCHA_DETECT_JS = void 0;
28
25
  exports.CAPTCHA_DETECT_JS = `(function() {
29
26
  var title = (document.title || '').toLowerCase();
30
- var titlePatterns = [
27
+
28
+ // Cloudflare JS interstitials — the stealth extension can clear these.
29
+ var cfTitlePatterns = [
31
30
  'just a moment', // Cloudflare interstitial (EN)
32
31
  'un momento', // CF Spanish
33
32
  'un instant', // CF French
34
33
  'einen moment', // CF German
35
34
  'checking your browser', // CF older / generic anti-bot
36
- 'attention required', // CF WAF block page
37
35
  'please wait', // generic "please wait while we check"
38
36
  ];
39
- for (var i = 0; i < titlePatterns.length; i++) {
40
- if (title.indexOf(titlePatterns[i]) !== -1) return 'captcha';
37
+ var cfSelectors = [
38
+ '#challenge-stage',
39
+ '#challenge-running',
40
+ '#cf-challenge-running',
41
+ '.cf-browser-verification',
42
+ '.cf-im-under-attack',
43
+ ];
44
+ for (var i = 0; i < cfTitlePatterns.length; i++) {
45
+ if (title.indexOf(cfTitlePatterns[i]) !== -1) return 'interstitial';
46
+ }
47
+ for (var j = 0; j < cfSelectors.length; j++) {
48
+ if (document.querySelector(cfSelectors[j])) return 'interstitial';
41
49
  }
42
50
 
43
- var selectors = [
44
- // Widget iframes
51
+ // Actual captcha widgets — need human intervention.
52
+ var captchaSelectors = [
45
53
  'iframe[src*="captcha-delivery"]', // DataDome
46
54
  'iframe[src*="geo.captcha"]', // DataDome geo
47
55
  'iframe[src*="recaptcha"]', // reCaptcha v2/v3
@@ -49,21 +57,15 @@ exports.CAPTCHA_DETECT_JS = `(function() {
49
57
  'iframe[src*="challenges.cloudflare"]', // Cloudflare Turnstile
50
58
  'iframe[src*="arkoselabs"]', // Arkose FunCaptcha
51
59
  'iframe[src*="funcaptcha"]',
52
- // Inline widgets
53
60
  '.g-recaptcha',
54
61
  '.h-captcha',
55
62
  '[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',
63
+ '#px-captcha', // PerimeterX / HUMAN
64
64
  ];
65
- for (var j = 0; j < selectors.length; j++) {
66
- if (document.querySelector(selectors[j])) return 'captcha';
65
+ // "Attention Required" is a CF WAF block not a JS interstitial, needs human.
66
+ if (title.indexOf('attention required') !== -1) return 'captcha';
67
+ for (var k = 0; k < captchaSelectors.length; k++) {
68
+ if (document.querySelector(captchaSelectors[k])) return 'captcha';
67
69
  }
68
70
  return 'clear';
69
71
  })()`;
@@ -29,17 +29,6 @@ const child_process_1 = require("child_process");
29
29
  const util_1 = require("util");
30
30
  const node_html_markdown_1 = require("node-html-markdown");
31
31
  const undici_1 = require("undici");
32
- // Cache ProxyAgent instances by URL so the same connection pool (and exit IP)
33
- // is reused across all fetch_website calls for the same proxy.
34
- const proxyAgentCache = new Map();
35
- function getProxyAgent(proxyUrl) {
36
- let agent = proxyAgentCache.get(proxyUrl);
37
- if (!agent) {
38
- agent = new undici_1.ProxyAgent(proxyUrl);
39
- proxyAgentCache.set(proxyUrl, agent);
40
- }
41
- return agent;
42
- }
43
32
  const os_1 = require("os");
44
33
  const crypto_1 = require("crypto");
45
34
  const takeover_state_js_1 = require("../takeover-state.js");
@@ -337,37 +326,6 @@ function getLanIp() {
337
326
  }
338
327
  return 'localhost';
339
328
  }
340
- /**
341
- * Stable hash of an agent ID string → non-negative integer.
342
- * Used so proxy assignment is deterministic by agent ID, not config order.
343
- */
344
- function hashAgentId(id) {
345
- let h = 0;
346
- for (const c of id)
347
- h = (Math.imul(31, h) + c.charCodeAt(0)) | 0;
348
- return Math.abs(h);
349
- }
350
- /**
351
- * Resolve the proxy for an agent.
352
- * Priority: agent.proxy config → GRANCLAW_PROXY_LIST (stable hash by agent ID) → undefined.
353
- * Hash-based assignment means the same agent always gets the same proxy even if
354
- * agents.config.json is reordered or new agents are added — so the Chrome daemon
355
- * always restarts on the same IP.
356
- * Only applies when GRANCLAW_PROXY_ENABLE=true or agent.proxy is explicitly set.
357
- */
358
- function resolveAgentProxy(agentId, configProxy) {
359
- if (configProxy)
360
- return configProxy;
361
- if (process.env.GRANCLAW_PROXY_ENABLE !== 'true')
362
- return undefined;
363
- const proxyList = process.env.GRANCLAW_PROXY_LIST;
364
- if (!proxyList)
365
- return undefined;
366
- const proxies = proxyList.split(',').map(p => p.trim()).filter(Boolean);
367
- if (proxies.length === 0)
368
- return undefined;
369
- return proxies[hashAgentId(agentId) % proxies.length];
370
- }
371
329
  async function runAgent(agent, message, onChunk, options) {
372
330
  const workspaceDir = path_1.default.resolve(config_js_1.REPO_ROOT, agent.workspaceDir);
373
331
  const channelId = options?.channelId ?? 'ui';
@@ -752,38 +710,43 @@ async function runAgent(agent, message, onChunk, options) {
752
710
  // Privileged commands (record, close, tab close for session 0,
753
711
  // session management) are rejected — the runtime owns those.
754
712
  /**
755
- * After a navigation command, check whether the page is blocked by any
756
- * bot/captcha wall (widget captcha OR Cloudflare-style JS interstitial)
757
- * and poll until CapMonster + the stealth extension have cleared it.
713
+ * After a navigation command, check whether the page is blocked.
758
714
  *
759
- * Budget is 45s Cloudflare's "Just a moment..." managed challenge often
760
- * takes 15–20s on a cold datacenter IP, and CapMonster-solved widgets can
761
- * take another pass, so shorter deadlines were causing real unblocks to
762
- * be reported as "unsolved" (regression verified on mariados 2026-04-15).
763
- *
764
- * Returns 'clear' (no captcha / solved), 'unsolved' (still blocked after timeout).
715
+ * 'interstitial' Cloudflare JS challenge; stealth can clear it, poll up to 45s.
716
+ * 'captcha' → Actual captcha widget; no auto-solver, request takeover immediately.
717
+ * 'clear' → No challenge detected.
765
718
  */
766
719
  async function waitForCaptchaResolution(bin, sessionId) {
767
720
  const evalArgv = (id) => ['--session', id, 'eval', captcha_detect_js_1.CAPTCHA_DETECT_JS];
768
721
  const check = async () => {
769
722
  try {
770
723
  const { stdout } = await execFileAsync(bin, evalArgv(sessionId), { timeout: 8_000 });
771
- return stdout.includes('"captcha"') || stdout.includes("'captcha'") || stdout.trim() === 'captcha';
724
+ const out = stdout.trim();
725
+ if (out.includes('captcha'))
726
+ return 'captcha';
727
+ if (out.includes('interstitial'))
728
+ return 'interstitial';
729
+ return 'clear';
772
730
  }
773
731
  catch {
774
- return false;
732
+ return 'clear';
775
733
  }
776
734
  };
777
- // Initial check — short delay to let page settle
778
735
  await new Promise(r => setTimeout(r, 1500));
779
- if (!await check())
736
+ const initial = await check();
737
+ if (initial === 'clear')
780
738
  return 'clear';
781
- // Challenge detected — poll every 2s until the page clears or 45s elapses.
739
+ if (initial === 'captcha')
740
+ return 'captcha';
741
+ // Cloudflare interstitial — poll every 2s until cleared or 45s elapses.
782
742
  const deadline = Date.now() + 45_000;
783
743
  while (Date.now() < deadline) {
784
744
  await new Promise(r => setTimeout(r, 2_000));
785
- if (!await check())
745
+ const result = await check();
746
+ if (result === 'clear')
786
747
  return 'clear';
748
+ if (result === 'captcha')
749
+ return 'captcha';
787
750
  }
788
751
  return 'unsolved';
789
752
  }
@@ -817,7 +780,7 @@ async function runAgent(agent, message, onChunk, options) {
817
780
  'Do not call `record start`, `record stop`, `close`, or `session` — the runtime manages those.',
818
781
  'You do not need to screenshot for audit — the whole session is recorded as video automatically.',
819
782
  'Saved logins persist automatically when the user has set up a profile via the dashboard Browser view.',
820
- '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.',
783
+ 'CAPTCHA & Cloudflare handling: the browser ships a stealth extension that handles Cloudflare JS interstitials ("Just a moment…") automatically (waits up to ~45s). If an actual CAPTCHA widget is detected (reCAPTCHA, hCaptcha, Turnstile, etc.), the runtime returns immediately with a warning call request_human_browser_takeover right away.',
821
784
  'Examples: {"command":"open","args":["https://example.com"]}, {"command":"click","args":["--ref","e12"]}, {"command":"fill","args":["--ref","e5","Alice"]}',
822
785
  ],
823
786
  parameters: {
@@ -870,11 +833,7 @@ async function runAgent(agent, message, onChunk, options) {
870
833
  if (fs_1.default.existsSync(profileDir)) {
871
834
  argv.push('--profile', profileDir);
872
835
  }
873
- const stealthOpts = {
874
- proxy: resolveAgentProxy(agent.id, agent.proxy),
875
- capmonsterKey: agent.capmonsterKey,
876
- };
877
- argv.push(...(0, stealth_js_1.stealthArgv)(stealthOpts));
836
+ argv.push(...(0, stealth_js_1.stealthArgv)());
878
837
  argv.push(command, ...args);
879
838
  try {
880
839
  const { stdout, stderr } = await execFileAsync(agentBrowserBin, argv, {
@@ -884,15 +843,22 @@ async function runAgent(agent, message, onChunk, options) {
884
843
  });
885
844
  (0, session_manager_js_1.appendCommand)(browserState.handle, `${command} ${args.join(' ')}`.trim());
886
845
  const out = stdout.trim() || stderr.trim() || 'ok';
887
- // After navigation, check for CAPTCHA and wait up to 30s for the
888
- // CapMonster extension to auto-solve it before returning to the agent.
889
846
  if (command === 'open' || command === 'reload') {
890
847
  const captchaResult = await waitForCaptchaResolution(agentBrowserBin, agent.id);
848
+ if (captchaResult === 'captcha') {
849
+ return {
850
+ content: [{
851
+ type: 'text',
852
+ text: out + '\n\n⚠️ CAPTCHA detected. ' +
853
+ 'Use request_human_browser_takeover to let the user solve it, or try a different URL.',
854
+ }],
855
+ };
856
+ }
891
857
  if (captchaResult === 'unsolved') {
892
858
  return {
893
859
  content: [{
894
860
  type: 'text',
895
- text: out + '\n\n⚠️ CAPTCHA detected and not auto-solved after 45s. ' +
861
+ text: out + '\n\n⚠️ Cloudflare interstitial not cleared after 45s. ' +
896
862
  'Use request_human_browser_takeover to let the user solve it, or try a different URL.',
897
863
  }],
898
864
  };
@@ -1140,9 +1106,7 @@ async function runAgent(agent, message, onChunk, options) {
1140
1106
  return { content: [{ type: 'text', text: `fetch_website (unblocker) error: ${err instanceof Error ? err.message : String(err)}` }] };
1141
1107
  }
1142
1108
  }
1143
- // Plain HTTP fetch — route through proxy if configured
1144
1109
  try {
1145
- const agentProxy = resolveAgentProxy(agent.id, agent.proxy);
1146
1110
  const fetchOpts = {
1147
1111
  headers: {
1148
1112
  'User-Agent': 'Mozilla/5.0 (compatible; GranClaw/1.0; +https://granclaw.com)',
@@ -1150,7 +1114,6 @@ async function runAgent(agent, message, onChunk, options) {
1150
1114
  },
1151
1115
  signal: AbortSignal.timeout(60_000),
1152
1116
  redirect: 'follow',
1153
- ...(agentProxy ? { dispatcher: getProxyAgent(agentProxy) } : {}),
1154
1117
  };
1155
1118
  const res = await (0, undici_1.fetch)(params.url, fetchOpts);
1156
1119
  if (!res.ok) {
@@ -33,11 +33,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
35
  exports.STEALTH_EXTENSION_DIR = void 0;
36
- exports.capmonsterExtensionDir = capmonsterExtensionDir;
37
36
  exports.stealthArgv = stealthArgv;
38
37
  exports.__resetStealthCacheForTests = __resetStealthCacheForTests;
39
38
  const fs_1 = __importDefault(require("fs"));
40
- const os_1 = __importDefault(require("os"));
41
39
  const path_1 = __importDefault(require("path"));
42
40
  const child_process_1 = require("child_process");
43
41
  // ── Extension dir ─────────────────────────────────────────────────────────────
@@ -59,55 +57,6 @@ function resolveExtensionDir() {
59
57
  return null;
60
58
  }
61
59
  exports.STEALTH_EXTENSION_DIR = resolveExtensionDir();
62
- // ── CapMonster extension ──────────────────────────────────────────────────────
63
- /**
64
- * Resolve the CapMonster extension directory (bundled in assets).
65
- * Returns null if not found.
66
- */
67
- function resolveCapmonsterExtensionDir() {
68
- const candidates = [
69
- path_1.default.resolve(__dirname, '../../assets/capmonster-extension'),
70
- path_1.default.resolve(__dirname, '../assets/capmonster-extension'),
71
- ];
72
- for (const candidate of candidates) {
73
- if (fs_1.default.existsSync(path_1.default.join(candidate, 'manifest.json')))
74
- return candidate;
75
- }
76
- return null;
77
- }
78
- const CAPMONSTER_BASE_DIR = resolveCapmonsterExtensionDir();
79
- /**
80
- * Return a CapMonster extension directory pre-configured with the given API key.
81
- * Writes a patched copy to os.tmpdir() so the base assets stay clean.
82
- * Result is cached per process — re-patched only if the key changes.
83
- */
84
- let _patchedCapmonsterDir = null;
85
- let _patchedCapmonsterKey = null;
86
- function capmonsterExtensionDir(apiKey) {
87
- if (!CAPMONSTER_BASE_DIR)
88
- return null;
89
- if (_patchedCapmonsterDir && _patchedCapmonsterKey === apiKey) {
90
- return _patchedCapmonsterDir;
91
- }
92
- try {
93
- const dest = path_1.default.join(os_1.default.tmpdir(), 'granclaw-capmonster-ext');
94
- // Copy base extension
95
- fs_1.default.cpSync(CAPMONSTER_BASE_DIR, dest, { recursive: true });
96
- // Patch defaultSettings.json with the API key
97
- const settingsPath = path_1.default.join(dest, 'defaultSettings.json');
98
- if (fs_1.default.existsSync(settingsPath)) {
99
- const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
100
- settings.clientKey = apiKey;
101
- fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
102
- }
103
- _patchedCapmonsterDir = dest;
104
- _patchedCapmonsterKey = apiKey;
105
- return dest;
106
- }
107
- catch {
108
- return null;
109
- }
110
- }
111
60
  // ── Chrome binary detection + UA derivation ───────────────────────────────────
112
61
  let cachedChromePath;
113
62
  function detectChromePath() {
@@ -234,24 +183,10 @@ function stealthArgv(options = {}) {
234
183
  if (exports.STEALTH_EXTENSION_DIR) {
235
184
  argv.push('--extension', exports.STEALTH_EXTENSION_DIR);
236
185
  }
237
- const capmonsterKey = options.capmonsterKey
238
- || process.env.CAPMONSTER_KEY
239
- || process.env.CAPMONSTER_API_KEY;
240
- const capmonsterEnabled = process.env.CAPMONSTER_ENABLED !== 'false';
241
- if (capmonsterKey && capmonsterEnabled) {
242
- const capmonsterDir = capmonsterExtensionDir(capmonsterKey);
243
- if (capmonsterDir)
244
- argv.push('--extension', capmonsterDir);
245
- }
246
186
  const chrome = detectChromePath();
247
187
  if (chrome) {
248
188
  argv.push('--executable-path', chrome);
249
189
  }
250
- // Route browser traffic through the agent's proxy if configured
251
- const proxy = options.proxy || process.env.GRANCLAW_PROXY;
252
- if (proxy) {
253
- argv.push('--proxy', proxy);
254
- }
255
190
  return argv;
256
191
  }
257
192
  // ── Test helpers ──────────────────────────────────────────────────────────────