chromeflow 0.10.4 → 0.10.6

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 (2) hide show
  1. package/bin/chromeflow.mjs +17 -10
  2. package/package.json +1 -1
@@ -25683,12 +25683,13 @@ ANTI-BOT SUBMIT CEILING \u2014 synthetic clicks on social/auth platforms (Reddit
25683
25683
  within_selector: external_exports.string().optional().describe(`Limit candidate matches to this CSS selector's subtree (mirrors find_text's scope_selector). Use to scope nth-counting to one section of a long form: click_element("Approve", nth=1, within_selector="#section-b"). Returns success=false with scope_missed=true if the selector does not match.`),
25684
25684
  near_text: external_exports.string().optional().describe("Find the nearest container whose heading starts with this text, then scope candidates to that container's subtree. Ignored when within_selector is set. Useful when the target section has no stable CSS selector but the heading is unique."),
25685
25685
  try_fiber: external_exports.boolean().optional().describe(`Opt-in last-resort fallback when silently_rejected fires. After the activity probe reports zero activity, chromeflow walks the React fiber tree from the matched element (up to 12 levels), finds the nearest \`__reactProps$.onClick\` prop, and invokes it with a minimal synthetic event. Now shadow-DOM-aware: when the element is inside a shadow root, the fiber walk searches for React root containers inside that shadow root instead of walking the light DOM. Returns fiber_attempted=true in the response when the path was taken. Do NOT default to this; reserve for repeat silently_rejected on a known-safe React site.`),
25686
- activity_timeout_ms: external_exports.number().int().min(500).optional().describe(`How long the activity probe watches for DOM mutations, focus changes, URL changes, or alert/toast/modal appearance after the click (default 1500ms). The probe returns early as soon as activity is detected, so the default adds only ~100ms on successful clicks. Increase to 2500-4000ms for buttons that trigger async API calls before producing visible DOM changes (e.g. "Collect Traces" on annotation dashboards). The probe reports "silently_rejected" when zero activity is seen within this window; a higher value reduces false negatives but slows genuine rejection detection.`),
25686
+ activity_timeout_ms: external_exports.number().int().min(500).optional().describe(`How long the activity probe watches for DOM mutations, focus changes, URL changes, or alert/toast/modal appearance after the click (default 1500ms). The probe returns early as soon as activity is detected, so the default adds only ~100ms on successful clicks. Increase to 2500-4000ms for buttons that trigger async API calls before producing visible DOM changes. The probe reports "silently_rejected" when zero activity is seen within this window; a higher value reduces false negatives but slows genuine rejection detection. For buttons where the click triggers a 3-5s API call with no immediate DOM change, use skip_activity_probe instead.`),
25687
+ skip_activity_probe: external_exports.boolean().optional().describe(`Skip the 1500ms activity probe AND all automatic fallbacks (tap gesture, pointer chain, DOM .click(), fiber walk) after the CDP click. The click is dispatched and success is returned immediately without verifying page activity. Use for buttons that trigger slow async API calls (3-5s+) before producing visible DOM changes, where the activity probe would falsely report "silently_rejected" and the fallback chain would double-fire. Verify state yourself via find_text or execute_script after an appropriate delay. WARNING: no automatic silent-rejection detection when this is set.`),
25687
25688
  via: external_exports.enum(["auto", "cdp", "fiber"]).optional().describe(`Click dispatch mode. "auto" (default): CDP click, then fiber fallback when try_fiber=true and the activity probe failed. "cdp": CDP click only, no fiber fallback ever. "fiber": skip the CDP bezier + activity probe entirely and invoke __reactProps$.onClick directly. Use "fiber" on React-heavy SPAs (Outlier-style dashboards) where you already know the site is fiber-only \u2014 cuts ~3 seconds of ceremony off the round trip. The fiber path is undocumented React internal access, prefer "auto" until you've confirmed the site needs it.`),
25688
25689
  in_dialog: external_exports.boolean().optional().describe(`Scope candidate matches to the topmost open dialog (\`[role=dialog]\`, \`[role=alertdialog]\`, or \`<dialog open>\`), highest z-index wins. Use when Radix/Headless UI dialogs portal to document.body and a generic textHint like "Cancel" would otherwise match the wrong button. Returns scope_missed=true when no dialog is open.`),
25689
25690
  dialog_query: external_exports.string().optional().describe(`Scope candidate matches to a specific dialog by heading or aria-label substring. Use when multiple dialogs are open and in_dialog (topmost) would pick the wrong one \u2014 e.g. click_element("Confirm", dialog_query="Delete account"). Mutually exclusive with in_dialog; dialog_query wins when both are set.`)
25690
25691
  },
25691
- async ({ textHint, selector, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber, activity_timeout_ms, via, in_dialog, dialog_query }) => {
25692
+ async ({ textHint, selector, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber, activity_timeout_ms, skip_activity_probe, via, in_dialog, dialog_query }) => {
25692
25693
  if (!textHint && !selector || textHint && selector) {
25693
25694
  return {
25694
25695
  content: [{ type: "text", text: "click_element requires exactly one of textHint or selector" }]
@@ -25699,7 +25700,7 @@ ANTI-BOT SUBMIT CEILING \u2014 synthetic clicks on social/auth platforms (Reddit
25699
25700
  let response;
25700
25701
  try {
25701
25702
  response = await bridge.request(
25702
- { type: "click_element", textHint, selector, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber, activity_timeout_ms, via, in_dialog, dialog_query },
25703
+ { type: "click_element", textHint, selector, nth, until_selector, until_url_contains, until_text_contains, until_url_changes, until_timeout_ms, expect_submit, within_selector, near_text, try_fiber, activity_timeout_ms, skip_activity_probe, via, in_dialog, dialog_query },
25703
25704
  wsTimeout
25704
25705
  );
25705
25706
  } catch (err) {
@@ -25761,34 +25762,40 @@ Current URL: ${activeTab.url}`;
25761
25762
  `Wait for the user to click (or interact with) the currently highlighted element, then return.
25762
25763
  Use this after highlighting a step so the flow advances automatically without the user returning to the chat.
25763
25764
  After this resolves, highlight the next step immediately.
25764
- If the click causes page navigation, this resolves when the new page finishes loading.`,
25765
+ If the click causes page navigation, this resolves when the new page finishes loading.
25766
+
25767
+ Pass \`redispatch: true\` to turn the user's gesture into a CDP-dispatched isTrusted=true click. When the user clicks the highlighted area, chromeflow captures the coordinates and re-dispatches a full humanlike CDP click (bezier approach, settle hover, pointer events) at those exact coordinates. This produces an isTrusted=true event that passes anti-bot checks. Use for buttons that reject all synthetic clicks (shadow DOM buttons checking isTrusted, annotation dashboard "Collect Traces" buttons) where highlight_region + wait_for_click normally works but only the user's real gesture fires the action. With redispatch, the user still clicks, but chromeflow re-fires via CDP so subsequent automation (activity probe, state verification) works normally.`,
25765
25768
  {
25766
- timeout: external_exports.number().optional().describe("Max seconds to wait for the click (default 120)")
25769
+ timeout: external_exports.number().optional().describe("Max seconds to wait for the click (default 120)"),
25770
+ redispatch: external_exports.boolean().optional().describe("Re-dispatch the user's click via CDP at the captured coordinates (isTrusted=true). The user clicks the highlighted area, chromeflow captures the (x, y) and fires a full humanlike CDP click sequence at those coordinates. Use for anti-bot buttons that reject all synthetic clicks. Returns redispatched=true and redispatch_activity=true/false in the response.")
25767
25771
  },
25768
- async ({ timeout = 120 }) => {
25772
+ async ({ timeout = 120, redispatch }) => {
25769
25773
  const watchMs = timeout * 1e3;
25770
25774
  const response = await bridge.request(
25771
25775
  {
25772
25776
  type: "start_click_watch",
25773
- timeout: watchMs
25777
+ timeout: watchMs,
25778
+ redispatch
25774
25779
  },
25775
25780
  watchMs + 5e3
25776
25781
  );
25777
25782
  const r = response;
25778
25783
  const targetLine = r.target ? `
25779
25784
  Clicked element: <${r.target.tag}>${r.target.text ? ` "${r.target.text}"` : ""} at (${r.target.x}, ${r.target.y}) \u2014 selector: ${r.target.selector}` : "";
25785
+ const redispatchLine = r.redispatched ? `
25786
+ CDP re-dispatched: isTrusted=true click at (${r.target?.x ?? 0}, ${r.target?.y ?? 0})${r.redispatch_activity ? " \u2014 activity detected" : " \u2014 no immediate activity (async action may still be processing)"}` : "";
25780
25787
  if (r.type === "navigation_complete") {
25781
25788
  return {
25782
25789
  content: [
25783
25790
  {
25784
25791
  type: "text",
25785
- text: `User clicked. Page navigated to: ${r.url ?? "(unknown)"}${targetLine}`
25792
+ text: `User clicked. Page navigated to: ${r.url ?? "(unknown)"}${targetLine}${redispatchLine}`
25786
25793
  }
25787
25794
  ]
25788
25795
  };
25789
25796
  }
25790
25797
  return {
25791
- content: [{ type: "text", text: `User clicked the highlighted element.${targetLine}` }]
25798
+ content: [{ type: "text", text: `User clicked the highlighted element.${targetLine}${redispatchLine}` }]
25792
25799
  };
25793
25800
  }
25794
25801
  );
@@ -26063,7 +26070,7 @@ ${lines.join("\n")}${shadowSection}` }] };
26063
26070
  }
26064
26071
 
26065
26072
  // packages/mcp-server/src/index.ts
26066
- var PACKAGE_VERSION = true ? "0.10.4" : "dev";
26073
+ var PACKAGE_VERSION = true ? "0.10.6" : "dev";
26067
26074
  main().catch((err) => {
26068
26075
  console.error("[chromeflow] Fatal error:", err);
26069
26076
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chromeflow",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
4
4
  "description": "MCP server for chromeflow — lets Claude Code or Codex CLI drive your real Chrome browser with sessions intact. Plugin install recommended; npx chromeflow for manual MCP wiring.",
5
5
  "type": "module",
6
6
  "main": "./bin/chromeflow.mjs",