browserclaw-agent 0.1.0
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/dist/agent-loop.d.ts +49 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +1574 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/content-policy.d.ts +5 -0
- package/dist/content-policy.d.ts.map +1 -0
- package/dist/content-policy.js +17 -0
- package/dist/content-policy.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/judge.d.ts +8 -0
- package/dist/judge.d.ts.map +1 -0
- package/dist/judge.js +57 -0
- package/dist/judge.js.map +1 -0
- package/dist/llm.d.ts +46 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +441 -0
- package/dist/llm.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +1 -0
- package/dist/parse-json-response.d.ts +6 -0
- package/dist/parse-json-response.d.ts.map +1 -0
- package/dist/parse-json-response.js +52 -0
- package/dist/parse-json-response.js.map +1 -0
- package/dist/skills/cdp-utils.d.ts +4 -0
- package/dist/skills/cdp-utils.d.ts.map +1 -0
- package/dist/skills/cdp-utils.js +25 -0
- package/dist/skills/cdp-utils.js.map +1 -0
- package/dist/skills/cloudflare-checkbox.d.ts +3 -0
- package/dist/skills/cloudflare-checkbox.d.ts.map +1 -0
- package/dist/skills/cloudflare-checkbox.js +108 -0
- package/dist/skills/cloudflare-checkbox.js.map +1 -0
- package/dist/skills/dismiss-popup.d.ts +4 -0
- package/dist/skills/dismiss-popup.d.ts.map +1 -0
- package/dist/skills/dismiss-popup.js +147 -0
- package/dist/skills/dismiss-popup.js.map +1 -0
- package/dist/skills/infinite-scroll.d.ts +32 -0
- package/dist/skills/infinite-scroll.d.ts.map +1 -0
- package/dist/skills/infinite-scroll.js +92 -0
- package/dist/skills/infinite-scroll.js.map +1 -0
- package/dist/skills/loop-detection.d.ts +16 -0
- package/dist/skills/loop-detection.d.ts.map +1 -0
- package/dist/skills/loop-detection.js +35 -0
- package/dist/skills/loop-detection.js.map +1 -0
- package/dist/skills/page-state.d.ts +11 -0
- package/dist/skills/page-state.d.ts.map +1 -0
- package/dist/skills/page-state.js +55 -0
- package/dist/skills/page-state.js.map +1 -0
- package/dist/skills/press-and-hold.d.ts +10 -0
- package/dist/skills/press-and-hold.d.ts.map +1 -0
- package/dist/skills/press-and-hold.js +183 -0
- package/dist/skills/press-and-hold.js.map +1 -0
- package/dist/skills/recovery.d.ts +20 -0
- package/dist/skills/recovery.d.ts.map +1 -0
- package/dist/skills/recovery.js +215 -0
- package/dist/skills/recovery.js.map +1 -0
- package/dist/skills/tab-manager.d.ts +18 -0
- package/dist/skills/tab-manager.d.ts.map +1 -0
- package/dist/skills/tab-manager.js +50 -0
- package/dist/skills/tab-manager.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/web-search.d.ts +2 -0
- package/dist/web-search.d.ts.map +1 -0
- package/dist/web-search.js +77 -0
- package/dist/web-search.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { logger } from '../logger.js';
|
|
2
|
+
const PRESS_HOLD_PATTERN = /press.*hold|hold.*to.*confirm/i;
|
|
3
|
+
const CLOUDFLARE_PATTERN = /performing security verification|cloudflare|verify you are human|just a moment/i;
|
|
4
|
+
const ANTI_BOT_PATTERN = /press.*hold|verify.*human|not a bot|captcha/i;
|
|
5
|
+
const BLOCKED_PATTERNS = {
|
|
6
|
+
press_and_hold: /press.*hold|verify.*human|not a bot|access.*denied/i,
|
|
7
|
+
cloudflare_checkbox: /performing security verification|verify you are human|just a moment/i,
|
|
8
|
+
};
|
|
9
|
+
function humanHoldMs() {
|
|
10
|
+
return 4000 + Math.floor(Math.random() * 6000); // 4-10 seconds
|
|
11
|
+
}
|
|
12
|
+
async function findButtonCoordinates(page) {
|
|
13
|
+
const result = await page.evaluate(`
|
|
14
|
+
(function() {
|
|
15
|
+
var PATTERN = /press.*hold|verify.*human|hold.*to.*confirm|not a bot/i;
|
|
16
|
+
var BUTTON_Y_OFFSET = 60;
|
|
17
|
+
|
|
18
|
+
function toCandidate(el, source, offsetX, offsetY) {
|
|
19
|
+
var rect = el.getBoundingClientRect();
|
|
20
|
+
return {
|
|
21
|
+
text: (el.innerText || '').trim().substring(0, 80),
|
|
22
|
+
width: rect.width,
|
|
23
|
+
height: rect.height,
|
|
24
|
+
x: Math.round(rect.left + rect.width / 2 + offsetX),
|
|
25
|
+
y: Math.round(rect.bottom + BUTTON_Y_OFFSET + offsetY),
|
|
26
|
+
tag: el.tagName,
|
|
27
|
+
source: source
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function matchingElements(root, source, offsetX, offsetY) {
|
|
32
|
+
var results = [];
|
|
33
|
+
var all = root.querySelectorAll('*');
|
|
34
|
+
for (var i = 0; i < all.length; i++) {
|
|
35
|
+
var el = all[i];
|
|
36
|
+
if (PATTERN.test((el.innerText || '').trim())) {
|
|
37
|
+
results.push(toCandidate(el, source, offsetX, offsetY));
|
|
38
|
+
}
|
|
39
|
+
if (el.shadowRoot) {
|
|
40
|
+
var shadowAll = el.shadowRoot.querySelectorAll('*');
|
|
41
|
+
for (var s = 0; s < shadowAll.length; s++) {
|
|
42
|
+
if (PATTERN.test((shadowAll[s].innerText || '').trim())) {
|
|
43
|
+
results.push(toCandidate(shadowAll[s], 'shadow', offsetX, offsetY));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function searchIframes() {
|
|
52
|
+
var results = [];
|
|
53
|
+
var iframes = document.querySelectorAll('iframe');
|
|
54
|
+
for (var i = 0; i < iframes.length; i++) {
|
|
55
|
+
try {
|
|
56
|
+
var doc = iframes[i].contentDocument;
|
|
57
|
+
if (doc && doc.body) {
|
|
58
|
+
var rect = iframes[i].getBoundingClientRect();
|
|
59
|
+
results = results.concat(matchingElements(doc, 'iframe', rect.left, rect.top));
|
|
60
|
+
}
|
|
61
|
+
} catch(e) {}
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function pickBest(candidates) {
|
|
67
|
+
var best = null;
|
|
68
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
69
|
+
var c = candidates[i];
|
|
70
|
+
if (c.width > 100 && c.height > 20 && c.height < 80) {
|
|
71
|
+
if (!best || c.height < best.height) best = c;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return best;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var candidates = matchingElements(document, 'dom', 0, 0).concat(searchIframes());
|
|
78
|
+
var best = pickBest(candidates);
|
|
79
|
+
return JSON.stringify({ found: !!best, best: best, candidates: candidates });
|
|
80
|
+
})()
|
|
81
|
+
`);
|
|
82
|
+
if (result === null || result === undefined || result === '')
|
|
83
|
+
return null;
|
|
84
|
+
const parsed = JSON.parse(result);
|
|
85
|
+
logger.info({
|
|
86
|
+
found: parsed.found,
|
|
87
|
+
candidateCount: parsed.candidates.length,
|
|
88
|
+
candidates: parsed.candidates.map((c) => ({ text: c.text, w: c.width, h: c.height, tag: c.tag })),
|
|
89
|
+
}, 'press-and-hold: button search');
|
|
90
|
+
if (!parsed.found || parsed.best === null)
|
|
91
|
+
return null;
|
|
92
|
+
return { x: parsed.best.x, y: parsed.best.y };
|
|
93
|
+
}
|
|
94
|
+
export async function getPageText(page) {
|
|
95
|
+
return (await page.evaluate(`
|
|
96
|
+
(function() {
|
|
97
|
+
var text = document.body.innerText || '';
|
|
98
|
+
var iframes = document.querySelectorAll('iframe');
|
|
99
|
+
for (var i = 0; i < iframes.length; i++) {
|
|
100
|
+
try {
|
|
101
|
+
if (iframes[i].contentDocument && iframes[i].contentDocument.body) {
|
|
102
|
+
text += ' ' + iframes[i].contentDocument.body.innerText;
|
|
103
|
+
}
|
|
104
|
+
} catch(e) {}
|
|
105
|
+
}
|
|
106
|
+
return text;
|
|
107
|
+
})()
|
|
108
|
+
`));
|
|
109
|
+
}
|
|
110
|
+
export async function isStillBlocked(page, type) {
|
|
111
|
+
if (type === null)
|
|
112
|
+
return false;
|
|
113
|
+
const pattern = BLOCKED_PATTERNS[type];
|
|
114
|
+
const result = await page.evaluate(`!!(document.body && document.body.innerText && document.body.innerText.match(${String(pattern)}))`);
|
|
115
|
+
return result === true;
|
|
116
|
+
}
|
|
117
|
+
export function detectAntiBot(domText) {
|
|
118
|
+
// Check press-and-hold first ā if DOM mentions press/hold, it's a press-and-hold challenge
|
|
119
|
+
// regardless of what the snapshot says
|
|
120
|
+
if (PRESS_HOLD_PATTERN.test(domText)) {
|
|
121
|
+
logger.info({ domTextPreview: domText.substring(0, 150) }, 'Anti-bot detected: press-and-hold');
|
|
122
|
+
return 'press_and_hold';
|
|
123
|
+
}
|
|
124
|
+
// Cloudflare-specific patterns (no press-and-hold in DOM text, already checked above)
|
|
125
|
+
if (CLOUDFLARE_PATTERN.test(domText)) {
|
|
126
|
+
logger.info({ domTextPreview: domText.substring(0, 150) }, 'Anti-bot detected: cloudflare checkbox');
|
|
127
|
+
return 'cloudflare_checkbox';
|
|
128
|
+
}
|
|
129
|
+
// Generic anti-bot (verify human, captcha, not a bot) ā treat as cloudflare-style checkbox
|
|
130
|
+
if (ANTI_BOT_PATTERN.test(domText)) {
|
|
131
|
+
logger.info({ domTextPreview: domText.substring(0, 150) }, 'Anti-bot detected: generic');
|
|
132
|
+
return 'cloudflare_checkbox';
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
export function enrichSnapshot(snapshot, domText, type) {
|
|
137
|
+
if (type === 'press_and_hold') {
|
|
138
|
+
return (snapshot +
|
|
139
|
+
`\n\n[ANTI-BOT OVERLAY DETECTED] The page has a press-and-hold verification overlay. The page text says: "${domText.substring(0, 200)}". Use press_and_hold to solve it.`);
|
|
140
|
+
}
|
|
141
|
+
if (type === 'cloudflare_checkbox') {
|
|
142
|
+
return (snapshot +
|
|
143
|
+
`\n\n[SECURITY VERIFICATION] The page has a Cloudflare or similar security check with a "Verify you are human" checkbox. Use click_cloudflare to solve it. If it fails after 2 attempts, use ask_user. Do NOT use press_and_hold.`);
|
|
144
|
+
}
|
|
145
|
+
return snapshot;
|
|
146
|
+
}
|
|
147
|
+
export async function pressAndHold(page, opts) {
|
|
148
|
+
try {
|
|
149
|
+
logger.info('press-and-hold: starting');
|
|
150
|
+
const coords = await findButtonCoordinates(page);
|
|
151
|
+
if (!coords) {
|
|
152
|
+
logger.info('press-and-hold: no suitable button found');
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const { x, y } = coords;
|
|
156
|
+
logger.info({ x, y }, 'press-and-hold: found button');
|
|
157
|
+
const urlBefore = await page.url();
|
|
158
|
+
const jitterX = x + Math.floor(Math.random() * 20) - 10;
|
|
159
|
+
const jitterY = y + Math.floor(Math.random() * 10) - 5;
|
|
160
|
+
const holdMs = opts?.holdMs ?? humanHoldMs();
|
|
161
|
+
const delay = 100 + Math.floor(Math.random() * 200);
|
|
162
|
+
logger.info({ x: jitterX, y: jitterY, holdMs, delay }, 'press-and-hold: pressing');
|
|
163
|
+
await page.pressAndHold(jitterX, jitterY, { delay, holdMs });
|
|
164
|
+
logger.info({ holdMs }, 'press-and-hold: released');
|
|
165
|
+
await page.waitFor({ timeMs: 2000 });
|
|
166
|
+
const stillBlocked = await isStillBlocked(page, 'press_and_hold');
|
|
167
|
+
if (stillBlocked) {
|
|
168
|
+
logger.info('press-and-hold: still blocked, refreshing page');
|
|
169
|
+
await page.goto(urlBefore);
|
|
170
|
+
await page.waitFor({ timeMs: 3000 });
|
|
171
|
+
const blockedAfterRefresh = await isStillBlocked(page, 'press_and_hold');
|
|
172
|
+
logger.info({ blockedAfterRefresh }, 'press-and-hold: result after refresh');
|
|
173
|
+
return !blockedAfterRefresh;
|
|
174
|
+
}
|
|
175
|
+
logger.info('press-and-hold: resolved');
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
logger.error({ err: err instanceof Error ? err.message : err }, 'press-and-hold: failed');
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=press-and-hold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"press-and-hold.js","sourceRoot":"","sources":["../../src/skills/press-and-hold.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentStep } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Analyzes recent agent history and suggests concrete recovery strategies
|
|
4
|
+
* when the agent appears stuck. Unlike loop-detection (which just nudges),
|
|
5
|
+
* this provides actionable escape plans based on failure patterns.
|
|
6
|
+
*/
|
|
7
|
+
export interface RecoveryStrategy {
|
|
8
|
+
diagnosis: string;
|
|
9
|
+
suggestions: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Analyze the last N steps to detect *why* the agent is stuck
|
|
13
|
+
* and suggest concrete recovery actions.
|
|
14
|
+
*/
|
|
15
|
+
export declare function diagnoseStuckAgent(history: AgentStep[], currentUrl: string): RecoveryStrategy | null;
|
|
16
|
+
/**
|
|
17
|
+
* Format a recovery strategy into a message that gets injected into the LLM context.
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatRecovery(strategy: RecoveryStrategy): string;
|
|
20
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/skills/recovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CA8BpG;AA8LD;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAOjE"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze the last N steps to detect *why* the agent is stuck
|
|
3
|
+
* and suggest concrete recovery actions.
|
|
4
|
+
*/
|
|
5
|
+
export function diagnoseStuckAgent(history, currentUrl) {
|
|
6
|
+
if (history.length < 6)
|
|
7
|
+
return null;
|
|
8
|
+
const recent = history.slice(-8);
|
|
9
|
+
// Pattern 1: Alternating between two failing approaches
|
|
10
|
+
const alternatingFailure = detectAlternatingFailures(recent);
|
|
11
|
+
if (alternatingFailure !== null)
|
|
12
|
+
return alternatingFailure;
|
|
13
|
+
// Pattern 2: Repeated clicks on different refs that all fail (wrong page section)
|
|
14
|
+
const scatterClicks = detectScatterClicks(recent);
|
|
15
|
+
if (scatterClicks !== null)
|
|
16
|
+
return scatterClicks;
|
|
17
|
+
// Pattern 3: Typing in search fields but never finding/clicking results
|
|
18
|
+
const searchWithoutResults = detectSearchWithoutResults(recent);
|
|
19
|
+
if (searchWithoutResults !== null)
|
|
20
|
+
return searchWithoutResults;
|
|
21
|
+
// Pattern 4: Stuck on same URL with no progress (diverse actions but no advancement)
|
|
22
|
+
const stagnation = detectStagnation(recent, currentUrl);
|
|
23
|
+
if (stagnation !== null)
|
|
24
|
+
return stagnation;
|
|
25
|
+
// Pattern 5: Navigation loops (going back and forth between pages)
|
|
26
|
+
const navLoop = detectNavigationLoop(recent);
|
|
27
|
+
if (navLoop !== null)
|
|
28
|
+
return navLoop;
|
|
29
|
+
// Pattern 6: Site is blocking automation (anti-bot actions or repeated access failures)
|
|
30
|
+
const siteBlocking = detectSiteBlocking(recent, currentUrl);
|
|
31
|
+
if (siteBlocking !== null)
|
|
32
|
+
return siteBlocking;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function detectAlternatingFailures(recent) {
|
|
36
|
+
const failed = recent.filter((s) => s.action.error_feedback !== undefined);
|
|
37
|
+
if (failed.length < 5)
|
|
38
|
+
return null;
|
|
39
|
+
// Check if the agent is alternating between 2-3 different actions that all fail
|
|
40
|
+
// Threshold is 5+ (higher than loop-detection's semantic check at 4) to avoid duplicate nudges
|
|
41
|
+
const failedActions = new Set(failed.map((s) => s.action.action));
|
|
42
|
+
if (failedActions.size <= 3 && failed.length >= 5) {
|
|
43
|
+
return {
|
|
44
|
+
diagnosis: 'You are alternating between approaches that all fail.',
|
|
45
|
+
suggestions: [
|
|
46
|
+
'STOP trying variations of the same approach.',
|
|
47
|
+
"Scroll the page to find different interactive elements you haven't tried.",
|
|
48
|
+
'Try navigating to the page via a different URL or site section.',
|
|
49
|
+
'If elements exist but clicks fail, there may be an overlay blocking them ā try pressing Escape first.',
|
|
50
|
+
"Use the site's main navigation menu or search bar instead of the current approach.",
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function detectScatterClicks(recent) {
|
|
57
|
+
const clicks = recent.filter((s) => s.action.action === 'click');
|
|
58
|
+
const failedClicks = clicks.filter((s) => s.action.error_feedback !== undefined);
|
|
59
|
+
if (failedClicks.length >= 3) {
|
|
60
|
+
// Many different refs tried, all failing
|
|
61
|
+
const uniqueRefs = new Set(failedClicks.map((s) => s.action.ref));
|
|
62
|
+
if (uniqueRefs.size >= 3) {
|
|
63
|
+
return {
|
|
64
|
+
diagnosis: 'Multiple click attempts on different elements are failing.',
|
|
65
|
+
suggestions: [
|
|
66
|
+
'The elements may not be clickable ā check if they are behind an overlay or iframe.',
|
|
67
|
+
'Try scrolling to reveal the elements before clicking.',
|
|
68
|
+
'The page structure may have changed since your last snapshot. Wait and get a fresh snapshot.',
|
|
69
|
+
'Try using keyboard navigation (Tab + Enter) instead of clicking.',
|
|
70
|
+
'Navigate directly to a URL if you know where you need to go.',
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function detectSearchWithoutResults(recent) {
|
|
78
|
+
const typeActions = recent.filter((s) => s.action.action === 'type');
|
|
79
|
+
const clickActions = recent.filter((s) => s.action.action === 'click');
|
|
80
|
+
// Only trigger if same field was typed into multiple times (re-searching) ā not for multi-field forms
|
|
81
|
+
const typedRefs = typeActions.map((s) => s.action.ref).filter(Boolean);
|
|
82
|
+
const uniqueTypedRefs = new Set(typedRefs);
|
|
83
|
+
const hasRepeatedSearches = typedRefs.length >= 2 && uniqueTypedRefs.size === 1;
|
|
84
|
+
if (hasRepeatedSearches && clickActions.length <= 1) {
|
|
85
|
+
return {
|
|
86
|
+
diagnosis: 'You have typed search queries but are not clicking on results.',
|
|
87
|
+
suggestions: [
|
|
88
|
+
'After typing, wait for autocomplete suggestions to appear and click the matching option.',
|
|
89
|
+
'If no autocomplete appears, press Enter to submit the search.',
|
|
90
|
+
'After submitting, scroll down to find the search results section.',
|
|
91
|
+
'The search results may be in a different format than expected ā look for list items, cards, or links.',
|
|
92
|
+
'Try a simpler or shorter search query.',
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function detectStagnation(recent, currentUrl) {
|
|
99
|
+
const sameUrlSteps = recent.filter((s) => s.url === currentUrl);
|
|
100
|
+
if (sameUrlSteps.length < 6)
|
|
101
|
+
return null;
|
|
102
|
+
// On the same URL for 6+ steps with mixed actions and majority errors
|
|
103
|
+
const actions = new Set(sameUrlSteps.map((s) => s.action.action));
|
|
104
|
+
const errorCount = sameUrlSteps.filter((s) => s.action.error_feedback !== undefined).length;
|
|
105
|
+
const errorRatio = errorCount / sameUrlSteps.length;
|
|
106
|
+
// Require majority errors ā a few failures on a long page is normal
|
|
107
|
+
if (errorRatio >= 0.5 && actions.size >= 2) {
|
|
108
|
+
return {
|
|
109
|
+
diagnosis: `You have been on this same page for ${String(sameUrlSteps.length)}+ steps without making progress.`,
|
|
110
|
+
suggestions: [
|
|
111
|
+
'This page may not have what you need. Navigate to a different section of the site.',
|
|
112
|
+
"Try using the site's search functionality to find the content directly.",
|
|
113
|
+
'Check if the page requires scrolling to reveal the content you need.',
|
|
114
|
+
'The page may require interaction in a specific order (e.g., select a category before results appear).',
|
|
115
|
+
'Consider starting fresh: navigate directly to the target URL if you can construct it.',
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/** Collapse consecutive same-URL steps into distinct visits. */
|
|
122
|
+
function collapseVisits(urls) {
|
|
123
|
+
const visits = [];
|
|
124
|
+
for (const url of urls) {
|
|
125
|
+
if (visits.length === 0 || visits[visits.length - 1] !== url) {
|
|
126
|
+
visits.push(url);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return visits;
|
|
130
|
+
}
|
|
131
|
+
function detectNavigationLoop(recent) {
|
|
132
|
+
const rawUrls = recent.map((s) => s.url).filter(Boolean);
|
|
133
|
+
// Collapse consecutive same-URL steps ā staying on a page for 7 steps is 1 visit, not 7
|
|
134
|
+
const visits = collapseVisits(rawUrls);
|
|
135
|
+
if (visits.length < 4)
|
|
136
|
+
return null;
|
|
137
|
+
// Detect AāBāAāB pattern (already collapsed, so consecutive entries always differ)
|
|
138
|
+
const urlSet = new Set(visits);
|
|
139
|
+
if (urlSet.size === 2 && visits.length >= 4) {
|
|
140
|
+
return {
|
|
141
|
+
diagnosis: 'You are navigating back and forth between two pages without making progress.',
|
|
142
|
+
suggestions: [
|
|
143
|
+
'Pick ONE of the two pages and commit to completing your task there.',
|
|
144
|
+
`Stay on the page that has the content you need and work through it systematically.`,
|
|
145
|
+
"If going back resets the page state (filters, scroll position), try a different approach that doesn't require going back.",
|
|
146
|
+
'Extract all needed information from each page before navigating away.',
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Detect revisiting the same URL 3+ times (distinct visits, not consecutive steps)
|
|
151
|
+
const visitCounts = new Map();
|
|
152
|
+
for (const url of visits) {
|
|
153
|
+
visitCounts.set(url, (visitCounts.get(url) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
for (const [url, count] of visitCounts) {
|
|
156
|
+
if (count >= 3) {
|
|
157
|
+
return {
|
|
158
|
+
diagnosis: `You have visited the same page 3+ times: ${url}`,
|
|
159
|
+
suggestions: [
|
|
160
|
+
'You are going in circles. Extract everything you need from this page in ONE visit.',
|
|
161
|
+
'If the page resets when you navigate away, find a way to accomplish your goal without leaving.',
|
|
162
|
+
'Consider a completely different approach to the task.',
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function detectSiteBlocking(recent, currentUrl) {
|
|
170
|
+
const antiBotActions = recent.filter((s) => s.action.action === 'press_and_hold' || s.action.action === 'click_cloudflare');
|
|
171
|
+
if (antiBotActions.length >= 2) {
|
|
172
|
+
let domain = '';
|
|
173
|
+
try {
|
|
174
|
+
domain = new URL(currentUrl).hostname;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
/* ignore */
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
diagnosis: `The site${domain !== '' ? ` (${domain})` : ''} is actively blocking automation ā you've triggered anti-bot challenges ${String(antiBotActions.length)} times.`,
|
|
181
|
+
suggestions: [
|
|
182
|
+
'STOP trying this site. It is blocking you and you will waste more steps.',
|
|
183
|
+
'Navigate to a DIFFERENT site that has the same information.',
|
|
184
|
+
'Go directly to the alternative site with your search terms in the URL (e.g. apartments.com/chelsea-new-york-ny).',
|
|
185
|
+
'If you already have partial results from this site, combine them with results from the next site.',
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// Detect repeated access-denied / empty page patterns
|
|
190
|
+
const errorSteps = recent.filter((s) => s.action.error_feedback !== undefined &&
|
|
191
|
+
/denied|forbidden|blocked|captcha|robot|bot.*detected/i.test(s.action.error_feedback));
|
|
192
|
+
if (errorSteps.length >= 3) {
|
|
193
|
+
return {
|
|
194
|
+
diagnosis: 'The site is returning access-denied or bot-detection errors.',
|
|
195
|
+
suggestions: [
|
|
196
|
+
'This site has detected automation. Do not continue here.',
|
|
197
|
+
'Navigate to an alternative site that has the same information.',
|
|
198
|
+
'Try a well-known, automation-friendly alternative.',
|
|
199
|
+
],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Format a recovery strategy into a message that gets injected into the LLM context.
|
|
206
|
+
*/
|
|
207
|
+
export function formatRecovery(strategy) {
|
|
208
|
+
let msg = `\nš§ RECOVERY NEEDED ā ${strategy.diagnosis}\n`;
|
|
209
|
+
msg += 'Try one of these strategies:\n';
|
|
210
|
+
for (const suggestion of strategy.suggestions) {
|
|
211
|
+
msg += ` ⢠${suggestion}\n`;
|
|
212
|
+
}
|
|
213
|
+
return msg;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/skills/recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CrawlPage } from 'browserclaw';
|
|
2
|
+
interface PageTarget {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
url: string;
|
|
6
|
+
title: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class TabManager {
|
|
9
|
+
private knownTabIds;
|
|
10
|
+
private cdpBaseUrl;
|
|
11
|
+
constructor(page: CrawlPage);
|
|
12
|
+
getPageTargets(): Promise<PageTarget[]>;
|
|
13
|
+
checkForNewTab(browser: {
|
|
14
|
+
page: (id: string) => CrawlPage;
|
|
15
|
+
}): Promise<CrawlPage | null>;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=tab-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-manager.d.ts","sourceRoot":"","sources":["../../src/skills/tab-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,UAAU,UAAU;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,EAAE,SAAS;IAKrB,cAAc,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAMvC,cAAc,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;CAsC9F"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getCdpBaseUrl, activateCdpTarget } from './cdp-utils.js';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
export class TabManager {
|
|
4
|
+
knownTabIds;
|
|
5
|
+
cdpBaseUrl;
|
|
6
|
+
constructor(page) {
|
|
7
|
+
this.knownTabIds = new Set([page.id]);
|
|
8
|
+
this.cdpBaseUrl = getCdpBaseUrl(page);
|
|
9
|
+
}
|
|
10
|
+
async getPageTargets() {
|
|
11
|
+
const res = await fetch(this.cdpBaseUrl + '/json');
|
|
12
|
+
const targets = (await res.json());
|
|
13
|
+
return targets.filter((t) => t.type === 'page');
|
|
14
|
+
}
|
|
15
|
+
async checkForNewTab(browser) {
|
|
16
|
+
try {
|
|
17
|
+
const targets = await this.getPageTargets();
|
|
18
|
+
const newTab = targets.find((t) => !this.knownTabIds.has(t.id));
|
|
19
|
+
if (!newTab) {
|
|
20
|
+
this.knownTabIds = new Set(targets.map((t) => t.id));
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const newPage = browser.page(newTab.id);
|
|
24
|
+
await activateCdpTarget(this.cdpBaseUrl, newTab.id);
|
|
25
|
+
// Wait for the tab to finish its initial load before switching
|
|
26
|
+
try {
|
|
27
|
+
await newPage.waitFor({ loadState: 'load', timeoutMs: 5000 });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
logger.info({ id: newTab.id }, 'tab-manager: new tab did not load in time, staying on current page');
|
|
31
|
+
this.knownTabIds = new Set(targets.map((t) => t.id));
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const url = await newPage.url();
|
|
35
|
+
if (url === '' || url === 'about:blank') {
|
|
36
|
+
logger.info({ id: newTab.id }, 'tab-manager: new tab has blank URL after load, staying on current page');
|
|
37
|
+
this.knownTabIds = new Set(targets.map((t) => t.id));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
this.knownTabIds = new Set(targets.map((t) => t.id));
|
|
41
|
+
logger.info({ title: newTab.title, url: newTab.url }, 'tab-manager: switched to new tab');
|
|
42
|
+
return newPage;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.error({ err }, 'tab-manager error');
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=tab-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-manager.js","sourceRoot":"","sources":["../../src/skills/tab-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAStC,MAAM,OAAO,UAAU;IACb,WAAW,CAAc;IACzB,UAAU,CAAS;IAE3B,YAAY,IAAe;QACzB,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,cAAc;QAClB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAiB,CAAC;QACnD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAA4C;QAC/D,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;YAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAEhE,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAExC,MAAM,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YAEpD,+DAA+D;YAC/D,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,oEAAoE,CAAC,CAAC;gBACrG,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;YAChC,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;gBACxC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,wEAAwE,CAAC,CAAC;gBACzG,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACrD,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,CAAC,WAAW,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,EAAE,kCAAkC,CAAC,CAAC;YAC1F,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,mBAAmB,CAAC,CAAC;YAC3C,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|