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.
- package/bin/chromeflow.mjs +17 -10
- package/package.json +1 -1
package/bin/chromeflow.mjs
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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",
|