smooth-operator-mcp 3.0.0 → 3.0.1

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/.env.example CHANGED
@@ -40,16 +40,16 @@ SMOOTH_OPERATOR_ALLOW_EVAL=true
40
40
  # these private roots by default.
41
41
  # SMOOTH_OPERATOR_ALLOWED_FILE_ROOTS=~/.smooth-operator/files,~/.smooth-operator/downloads
42
42
 
43
- # Optional automation-control baseline. The native default is enabled; set false
44
- # to preserve raw browser automation signals.
43
+ # Compatibility baseline. Browser identity and automation signals remain native;
44
+ # set false to skip the explicit-viewport compatibility script.
45
45
  # SMOOTH_OPERATOR_STEALTH_ENABLED=true
46
46
  # Stealth profile: balanced (default) | max (accepted compatibility names; same supported patch set)
47
47
  # SMOOTH_OPERATOR_STEALTH_PROFILE=balanced
48
48
  # Add opt-in GPU launch flags (does not make an identity/coherence guarantee)
49
49
  # SMOOTH_OPERATOR_STEALTH_GPU=false
50
- # Behavioral realism defaults on with short bounded timing; set false for the
51
- # fastest raw interaction path or override to decouple from stealth.
52
- # SMOOTH_OPERATOR_BEHAVIOR_ENABLED=true
50
+ # Behavioral timing is off for the fastest deterministic path; set true only
51
+ # when a site or workflow explicitly needs it.
52
+ # SMOOTH_OPERATOR_BEHAVIOR_ENABLED=false
53
53
 
54
54
  # HTTP is opt-in; use a strong token before enabling remote access
55
55
  # SMOOTH_OPERATOR_TRANSPORT=http
package/README.md CHANGED
@@ -44,9 +44,12 @@ Ask: *“Scrape pricing into a table”*, *“Fill this form with ~/resume.pdf
44
44
 
45
45
  ## How to use
46
46
 
47
- Talk to your harness normally. It can call `browser_navigate` → `browser_snapshot` → `browser_click` and the rest of the MCP surface as needed. For a challenge, `browser_solve_challenge` returns fresh bounded visual/state evidence for the connected AI; the AI uses ordinary browser actions and calls it again to verify the final classification. Human handoff remains available for logins or challenges that require the user.
47
+ Talk to your harness normally. It can call `browser_navigate` → `browser_snapshot` → `browser_click` and the rest of the MCP surface as needed. For a challenge, `browser_solve_challenge` returns fresh bounded visual/state evidence and an attempt budget; the connected AI keeps using ordinary browser actions and calls it again until the final classification is clear or the budget is exhausted. Human handoff remains available only as an explicit final option.
48
48
 
49
- Stealth and short behavioral timing are enabled in the recommended native profile. Page JavaScript is also available by default; set the explicit environment flags to `false` when a stricter profile is needed. See `docs/STEALTH-GUIDE.md` for details and responsible use.
49
+ Browser identity remains native, page JavaScript is available by default, and
50
+ behavioral timing is off for fast deterministic input. Set the explicit
51
+ environment flags to change those choices. See `docs/STEALTH-GUIDE.md` for
52
+ details and responsible use.
50
53
 
51
54
  For the fastest supported operation, keep behavioral timing off with
52
55
  `SMOOTH_OPERATOR_BEHAVIOR_ENABLED=false`; the native browser remains bounded and
@@ -397,7 +397,7 @@ var SERVER_VERSION;
397
397
  var init_version = __esm({
398
398
  "src/server/version.ts"() {
399
399
  "use strict";
400
- SERVER_VERSION = "3.0.0";
400
+ SERVER_VERSION = "3.0.1";
401
401
  }
402
402
  });
403
403
 
@@ -1554,7 +1554,7 @@ function writeSummary(ui, harness, choices) {
1554
1554
  ["Blocked sites", choices.blockedDomains.length ? choices.blockedDomains.join(", ") : "none"],
1555
1555
  ["Page JavaScript", choices.allowEval ? "enabled" : "off"],
1556
1556
  ["Stealth baseline", "enabled (balanced)"],
1557
- ["Behavioral realism", "enabled"],
1557
+ ["Behavioral timing", "off (fast native input)"],
1558
1558
  ["Data directory", choices.dataDir]
1559
1559
  ]
1560
1560
  ]);
@@ -1629,7 +1629,7 @@ async function persistWizardConfig(rawChoices, homeDir) {
1629
1629
  enabled: true,
1630
1630
  profile: "balanced",
1631
1631
  gpu: false,
1632
- behaviorEnabled: true
1632
+ behaviorEnabled: false
1633
1633
  };
1634
1634
  const config = { ...previous, browser: browserSection, security: securitySection, stealth: stealthSection };
1635
1635
  delete config.captchaSolver;
@@ -2570,7 +2570,7 @@ function loadServerConfig(args = [], environment = env, homeDirectory = homedir(
2570
2570
  enabled: stealthEnabled,
2571
2571
  profile: environment.SMOOTH_OPERATOR_STEALTH_PROFILE ?? nestedStealth.profile ?? "balanced",
2572
2572
  gpu: parseBoolean(environment.SMOOTH_OPERATOR_STEALTH_GPU, nestedStealth.gpu ?? false),
2573
- behaviorEnabled: environment.SMOOTH_OPERATOR_BEHAVIOR_ENABLED === void 0 ? nestedStealth.behaviorEnabled ?? true : parseBoolean(environment.SMOOTH_OPERATOR_BEHAVIOR_ENABLED, stealthEnabled)
2573
+ behaviorEnabled: environment.SMOOTH_OPERATOR_BEHAVIOR_ENABLED === void 0 ? nestedStealth.behaviorEnabled ?? false : parseBoolean(environment.SMOOTH_OPERATOR_BEHAVIOR_ENABLED, stealthEnabled)
2574
2574
  };
2575
2575
  const config = {
2576
2576
  transport: argValue("--transport") ?? environment.SMOOTH_OPERATOR_TRANSPORT ?? fileConfig.transport ?? "stdio",
@@ -2820,6 +2820,7 @@ var BrowserActionFieldsSchema = z2.object({
2820
2820
  verify: z2.boolean().optional(),
2821
2821
  durationMs: z2.number().int().min(0).max(3e4).optional(),
2822
2822
  pollMs: z2.number().int().min(250).max(1e4).optional(),
2823
+ maxAttempts: z2.number().int().min(1).max(100).optional(),
2823
2824
  optionValue: BoundedString(2e3).optional(),
2824
2825
  optionValues: z2.array(BoundedString(2e3)).min(1).max(200).optional(),
2825
2826
  cookieName: BoundedString(256).optional(),
@@ -3256,7 +3257,8 @@ var SolveChallengeRequestSchema = z2.object({
3256
3257
  full: z2.boolean().optional(),
3257
3258
  maxDimension: z2.number().int().min(1).max(2e4).optional(),
3258
3259
  max_dim: z2.number().int().min(1).max(2e4).optional(),
3259
- maxChars: z2.number().int().min(1e3).max(MCP_PAGE_TEXT_MAX_CHARS).optional()
3260
+ maxChars: z2.number().int().min(1e3).max(MCP_PAGE_TEXT_MAX_CHARS).optional(),
3261
+ maxAttempts: z2.number().int().min(1).max(100).optional()
3260
3262
  }).strict().superRefine((input, context) => {
3261
3263
  if (input.includeScreenshot !== void 0 && input.include_screenshot !== void 0) {
3262
3264
  context.addIssue({ code: "custom", message: "Provide includeScreenshot or include_screenshot, not both." });
@@ -3624,7 +3626,7 @@ var MCP_INSTRUCTIONS = [
3624
3626
  "For open shadow roots, Puppeteer pierce/ selectors may be used explicitly; closed shadow roots remain unavailable.",
3625
3627
  "Use browser_batch for short validated sequences, but keep destructive actions separate when user confirmation is needed.",
3626
3628
  "Use an efficient observe -> act -> verify loop: observe with browser_snapshot or browser_get_state, perform one bounded browser action, then observe again to verify the resulting state. Keep refs and indexes fresh after navigation, scrolling, or DOM changes; parallelize only independent read-only observations.",
3627
- "browser_solve_challenge is an internal connected-AI loop. It collects a fresh challenge classification plus bounded visual/state evidence, then the connected AI uses normal browser actions and calls it again to verify. Never claim a challenge is solved unless the final classification explicitly reports it absent; unknown or failed classification is not success.",
3629
+ "browser_solve_challenge is an internal connected-AI loop. Each call is one bounded verification cycle and returns fresh visual/state evidence plus attemptsRemaining; the connected AI should keep using normal browser actions and call it again until the final classification explicitly reports the challenge absent or automation_exhausted. Never claim a challenge is solved from a present, unknown, or failed classification. Human handoff is only an explicit final option after exhaustion.",
3628
3630
  "The server contains no LLM or agent planner; the MCP client is responsible for reasoning, retries, and task completion."
3629
3631
  ].join(" ");
3630
3632
  function createMcpServer(runtime) {
@@ -3784,7 +3786,7 @@ function registerBrowserTools(server, runtime) {
3784
3786
  "browser_solve_challenge",
3785
3787
  {
3786
3788
  title: "Solve a web challenge",
3787
- description: "Run the internal connected-AI challenge loop: collect fresh challenge classification and bounded visual/state evidence, let the connected AI use normal browser actions, then call again to verify. The result is successful only when the final classification explicitly reports the challenge absent.",
3789
+ description: "Run one cycle of the internal connected-AI challenge loop. Collect fresh bounded visual/state evidence, use normal browser actions, and call again until the challenge is explicitly absent or the bounded attempt budget is exhausted. No external solver or token injection is used.",
3788
3790
  inputSchema: SolveChallengeRequestSchema,
3789
3791
  annotations: BROWSER_READ_ONLY
3790
3792
  },
@@ -4732,81 +4734,16 @@ function boundedList(values) {
4732
4734
  }
4733
4735
 
4734
4736
  // src/server/browser/stealth.ts
4735
- var STEALTH_BASELINE_ARGS = [
4736
- "--disable-blink-features=AutomationControlled"
4737
- // hide navigator.webdriver at the C++ source
4738
- ];
4737
+ var STEALTH_BASELINE_ARGS = [];
4739
4738
  function buildStealthInitScript(profile, options = {}) {
4740
4739
  const { width, height } = profile.viewport;
4741
4740
  const applyViewport = options.applyViewport === true;
4742
- const head = `
4743
- // ---- shared helpers (ported, minimal) ----
4744
- function makeNativeString(fnName) {
4745
- return 'function ' + fnName + '() { [native code] }';
4746
- }
4747
- function patchToString(target, fnStr) {
4748
- try {
4749
- Object.defineProperty(target, 'toString', {
4750
- configurable: true,
4751
- writable: true,
4752
- value: function toString() { return fnStr; }
4753
- });
4754
- } catch (e) {}
4755
- }
4756
- function stripProxyFromErrors(fn) {
4757
- try {
4758
- return new Proxy(fn, {
4759
- apply: function applyTrap(target, thisArg, args) {
4760
- try { return Reflect.apply(target, thisArg, args); }
4761
- catch (e) { throw e; }
4762
- }
4763
- });
4764
- } catch (e) { return fn; }
4765
- }
4766
-
4767
- // ---- supported runtime values (only explicit configuration is interpolated) ----
4741
+ const source = `
4742
+ // Only an explicitly configured viewport is applied. Browser identity and
4743
+ // native automation signals remain untouched.
4768
4744
  var APPLY_VIEWPORT = ${String(applyViewport)};
4769
4745
  var VIEWPORT = { width: ${width}, height: ${height} };`;
4770
- const balanced = `
4771
- // 1. navigator.webdriver \u2014 belt-and-suspenders (the launch flag is primary).
4772
- try {
4773
- var navProto = Object.getPrototypeOf(navigator);
4774
- if (navProto && Object.prototype.hasOwnProperty.call(navProto, 'webdriver')) {
4775
- delete navProto.webdriver;
4776
- }
4777
- } catch (e) {}
4778
-
4779
- // 2. navigator.permissions.query \u2014 resolve the "impossible combination".
4780
- try {
4781
- if (navigator.permissions && typeof navigator.permissions.query === 'function') {
4782
- var perms = navigator.permissions;
4783
- var originalQuery = perms.query.bind(perms);
4784
- perms.query = function (query) {
4785
- if (query && query.name === 'notifications' &&
4786
- window.location && window.location.protocol !== 'https:') {
4787
- try {
4788
- if (typeof PermissionStatus !== 'undefined') {
4789
- return Promise.resolve(new PermissionStatus({ state: 'denied' }));
4790
- }
4791
- } catch (e) {}
4792
- }
4793
- return originalQuery(query);
4794
- };
4795
- }
4796
- } catch (e) {}
4797
-
4798
- // 3. toString / Proxy trace hiding \u2014 the patched getters hide themselves.
4799
- try {
4800
- if (navigator.permissions && typeof navigator.permissions.query === 'function') {
4801
- var hiddenQuery = stripProxyFromErrors(navigator.permissions.query);
4802
- patchToString(hiddenQuery, makeNativeString('query'));
4803
- navigator.permissions.query = hiddenQuery;
4804
- }
4805
- } catch (e) {}
4806
-
4807
- // Coherence: screen dimensions track an explicitly configured launch
4808
- // viewport (guarded, best-effort). Without that explicit input, leave the
4809
- // browser's real dimensions untouched.
4746
+ const viewport = `
4810
4747
  try {
4811
4748
  if (APPLY_VIEWPORT && window && typeof window.innerWidth === 'number') {
4812
4749
  Object.defineProperty(window, 'innerWidth', {
@@ -4818,8 +4755,8 @@ function buildStealthInitScript(profile, options = {}) {
4818
4755
  }
4819
4756
  } catch (e) {}`;
4820
4757
  return `(function () {
4821
- ${head}
4822
- ${balanced}
4758
+ ${source}
4759
+ ${viewport}
4823
4760
  })();
4824
4761
  `;
4825
4762
  }
@@ -4868,67 +4805,10 @@ function nativeBrowserLaunchArgs(options = {}) {
4868
4805
  init_discovery();
4869
4806
 
4870
4807
  // src/server/browser/fingerprints.ts
4871
- var DEFAULT_PROFILE = "balanced";
4872
- var DEFAULT_VERSION = 124;
4873
- var DEFAULT_PLATFORM = "Windows";
4874
- var DEFAULT_LANGUAGE = "en-US";
4875
- var DEFAULT_TIME_ZONE = "America/New_York";
4876
4808
  var DEFAULT_VIEWPORT_WIDTH = 1920;
4877
4809
  var DEFAULT_VIEWPORT_HEIGHT = 1080;
4878
- var DEFAULT_SEED = 0;
4879
- var PLATFORM_SEGMENTS = {
4880
- Windows: "Windows NT 10.0; Win64; x64",
4881
- macOS: "Mac OS X 10_15_7",
4882
- Linux: "X11; Linux x86_64"
4883
- };
4884
- var HARDWARE_CONCURRENCY_SET = [2, 4, 8, 16, 32];
4885
- var DEVICEMEMORY_SET = [1, 2, 4, 8];
4886
- var MAX_TOUCH_POINTS_SET = [0, 5, 10];
4887
4810
  function buildFingerprintProfile(options = {}) {
4888
- const profile = options.profile ?? DEFAULT_PROFILE;
4889
- const version = normalizeVersion(options.version);
4890
- const platform3 = normalizePlatform(options.platform);
4891
- const viewport = normalizeViewport(options.viewport);
4892
- const language = options.language ?? DEFAULT_LANGUAGE;
4893
- const seed = normalizeSeed(options.seed);
4894
- const versionStr = String(version);
4895
- const userAgent = `Mozilla/5.0 (${PLATFORM_SEGMENTS[platform3]}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${versionStr}.0.0.0 Safari/537.36`;
4896
- const brands = [
4897
- { brand: "Chromium", version: versionStr },
4898
- { brand: "Google Chrome", version: versionStr },
4899
- { brand: "Not=A?Brand", version: "8" }
4900
- ];
4901
- const fullVersionList = {
4902
- Chromium: versionStr,
4903
- "Google Chrome": versionStr
4904
- };
4905
- const profileWithLanguages = {
4906
- version,
4907
- userAgent,
4908
- platform: platform3,
4909
- mobile: false,
4910
- brands,
4911
- fullVersionList,
4912
- languages: buildLanguages(language),
4913
- acceptLanguage: `${language},en;q=0.9`,
4914
- viewport
4915
- };
4916
- if (profile === "max") {
4917
- return {
4918
- ...profileWithLanguages,
4919
- hardwareConcurrency: pickFromValidSet(HARDWARE_CONCURRENCY_SET, seed),
4920
- deviceMemory: pickFromValidSet(DEVICEMEMORY_SET, seed),
4921
- maxTouchPoints: pickFromValidSet(MAX_TOUCH_POINTS_SET, seed),
4922
- timeZone: options.timeZone ?? DEFAULT_TIME_ZONE
4923
- };
4924
- }
4925
- return profileWithLanguages;
4926
- }
4927
- function normalizeVersion(version) {
4928
- return typeof version === "number" && Number.isFinite(version) && version > 0 ? Math.floor(version) : DEFAULT_VERSION;
4929
- }
4930
- function normalizePlatform(platform3) {
4931
- return platform3 && platform3 in PLATFORM_SEGMENTS ? platform3 : DEFAULT_PLATFORM;
4811
+ return { viewport: normalizeViewport(options.viewport) };
4932
4812
  }
4933
4813
  function normalizeViewport(viewport) {
4934
4814
  if (viewport && Number.isFinite(viewport.width) && Number.isFinite(viewport.height) && viewport.width > 0 && viewport.height > 0) {
@@ -4936,16 +4816,6 @@ function normalizeViewport(viewport) {
4936
4816
  }
4937
4817
  return { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT };
4938
4818
  }
4939
- function normalizeSeed(seed) {
4940
- return typeof seed === "number" && Number.isFinite(seed) ? seed : DEFAULT_SEED;
4941
- }
4942
- function buildLanguages(language) {
4943
- const parts = language.split(/[-_]/);
4944
- return parts.length > 1 ? [language, parts[0].toLowerCase()] : [language];
4945
- }
4946
- function pickFromValidSet(set, seed) {
4947
- return set[Math.floor(Math.abs(seed)) % set.length];
4948
- }
4949
4819
 
4950
4820
  // src/server/browser/utils.ts
4951
4821
  var SENSITIVE_URL_PART = /(access[_-]?token|api[_-]?key|auth|code|credential|jwt|nonce|otp|password|secret|session|sig(?:nature)?|token)/i;
@@ -5051,6 +4921,8 @@ var MAX_DOM_TRAVERSAL_NODES = 2e4;
5051
4921
  var MAX_TEXT_SCAN_CHARS = 5e5;
5052
4922
  var MAX_MARKUP_EVIDENCE_CHARS = 12e4;
5053
4923
  var CHALLENGE_AI_GUIDANCE = "Use normal browser click, input, scroll, or key tools on the visible challenge controls, then call solve_challenge again to verify that the challenge is cleared.";
4924
+ var CHALLENGE_DEFAULT_MAX_ATTEMPTS = 32;
4925
+ var CHALLENGE_MAX_ATTEMPTS = 100;
5054
4926
  var MAX_DOWNLOAD_ENTRIES = 100;
5055
4927
  var TARGET_GUARD_MAX_REQUEST_IDS = 128;
5056
4928
  var CLICK_SETTLE_TIMEOUT_MS = 10;
@@ -8338,6 +8210,7 @@ var BrowserService = class {
8338
8210
  this.clearTargetGuardNavigationError(state.page);
8339
8211
  state.policyVerifiedUrls?.clear();
8340
8212
  state.challengeStatus = void 0;
8213
+ state.challengeAttempts = 0;
8341
8214
  } catch (error) {
8342
8215
  this.logger.debug("Blocked navigation recovery could not restore a blank page", { pageId: state.id, error: String(error) });
8343
8216
  }
@@ -8570,7 +8443,7 @@ var BrowserService = class {
8570
8443
  state.stealthInjected = true;
8571
8444
  } catch (error) {
8572
8445
  throwIfAborted(signal);
8573
- throw new AppError("STEALTH_INITIALIZATION_FAILED", "The stealth fingerprint init script could not be injected.", { retryable: true, cause: error });
8446
+ throw new AppError("STEALTH_INITIALIZATION_FAILED", "The browser compatibility script could not be injected.", { retryable: true, cause: error });
8574
8447
  }
8575
8448
  }
8576
8449
  await this.releaseTargetGuardForPage(state.page);
@@ -8751,6 +8624,7 @@ var BrowserService = class {
8751
8624
  const mainFrame = state.page.mainFrame();
8752
8625
  if (frame === mainFrame) {
8753
8626
  state.challengeStatus = void 0;
8627
+ state.challengeAttempts = 0;
8754
8628
  }
8755
8629
  } catch (error) {
8756
8630
  this.logger.debug("Browser frame navigation event was unavailable after page disposal", { pageId: state.id, error: String(error) });
@@ -10372,12 +10246,15 @@ var BrowserService = class {
10372
10246
  async solveChallenge(state, action, signal) {
10373
10247
  throwIfAborted(signal);
10374
10248
  const previousChallengeStatus = state.challengeStatus;
10249
+ const requestedMaxAttempts = action.maxAttempts;
10250
+ const maxAttempts = Number.isFinite(requestedMaxAttempts) ? Math.min(CHALLENGE_MAX_ATTEMPTS, Math.max(1, Math.floor(requestedMaxAttempts))) : CHALLENGE_DEFAULT_MAX_ATTEMPTS;
10375
10251
  const detection = await this.detectChallenge(state, signal);
10376
10252
  if (isChallengeUnknown(detection)) {
10377
10253
  state.challengeStatus = "unknown";
10378
10254
  return {
10379
10255
  solved: false,
10380
10256
  verified: false,
10257
+ challengeState: "unknown",
10381
10258
  resolution: "challenge_state_unverified",
10382
10259
  verification: "unknown",
10383
10260
  workflow: "verification_unavailable",
@@ -10389,18 +10266,41 @@ var BrowserService = class {
10389
10266
  }
10390
10267
  if (isChallengeAbsent(detection)) {
10391
10268
  const cleared = previousChallengeStatus === "present";
10269
+ const attempts2 = state.challengeAttempts ?? 0;
10392
10270
  state.challengeStatus = "absent";
10271
+ state.challengeAttempts = 0;
10393
10272
  return {
10394
10273
  solved: true,
10395
10274
  verified: true,
10275
+ challengeState: "clear",
10396
10276
  resolution: cleared ? "challenge_cleared" : "no_challenge",
10397
10277
  verification: "verified",
10398
10278
  workflow: "verified",
10399
10279
  pageId: state.id,
10280
+ ...attempts2 > 0 ? { attempts: attempts2 } : {},
10400
10281
  classification: detection
10401
10282
  };
10402
10283
  }
10403
10284
  state.challengeStatus = "present";
10285
+ const attempts = (state.challengeAttempts ?? 0) + 1;
10286
+ state.challengeAttempts = attempts;
10287
+ if (attempts > maxAttempts) {
10288
+ return {
10289
+ solved: false,
10290
+ verified: true,
10291
+ challengeState: "present",
10292
+ resolution: "automation_exhausted",
10293
+ verification: "challenge_present",
10294
+ workflow: "human_handoff_available",
10295
+ pageId: state.id,
10296
+ attempts: attempts - 1,
10297
+ maxAttempts,
10298
+ attemptsRemaining: 0,
10299
+ classification: detection,
10300
+ nextAction: "Automation attempts are exhausted. Human handoff is available if the site permits it.",
10301
+ guidance: "Automation attempts are exhausted. Human handoff is available if the site permits it."
10302
+ };
10303
+ }
10404
10304
  const includeScreenshot = action.includeScreenshot ?? action.include_screenshot ?? true;
10405
10305
  const requestedMaxDimension = action.maxDimension ?? action.max_dim;
10406
10306
  const maxDimension = Number.isFinite(requestedMaxDimension) ? Math.min(1600, Math.max(100, Math.floor(requestedMaxDimension))) : 1600;
@@ -10430,6 +10330,9 @@ var BrowserService = class {
10430
10330
  snapshotId: snapshot.snapshotId,
10431
10331
  domRevision: snapshot.domRevision,
10432
10332
  viewport: snapshot.viewport,
10333
+ attempts,
10334
+ maxAttempts,
10335
+ attemptsRemaining: Math.max(0, maxAttempts - attempts),
10433
10336
  refs: stableRefs,
10434
10337
  interactive: stableRefs,
10435
10338
  classification: detection,
@@ -12166,7 +12069,7 @@ var ServerRuntime = class _ServerRuntime {
12166
12069
  headedBrowser: true,
12167
12070
  pageEvaluation: true,
12168
12071
  stealth: true,
12169
- behavioralTiming: true
12072
+ behavioralTiming: false
12170
12073
  },
12171
12074
  features: {
12172
12075
  localBrowserTools: "available",