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.
Files changed (77) hide show
  1. package/dist/agent-loop.d.ts +49 -0
  2. package/dist/agent-loop.d.ts.map +1 -0
  3. package/dist/agent-loop.js +1574 -0
  4. package/dist/agent-loop.js.map +1 -0
  5. package/dist/config.d.ts +33 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +73 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/content-policy.d.ts +5 -0
  10. package/dist/content-policy.d.ts.map +1 -0
  11. package/dist/content-policy.js +17 -0
  12. package/dist/content-policy.js.map +1 -0
  13. package/dist/index.d.ts +18 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +14 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/judge.d.ts +8 -0
  18. package/dist/judge.d.ts.map +1 -0
  19. package/dist/judge.js +57 -0
  20. package/dist/judge.js.map +1 -0
  21. package/dist/llm.d.ts +46 -0
  22. package/dist/llm.d.ts.map +1 -0
  23. package/dist/llm.js +441 -0
  24. package/dist/llm.js.map +1 -0
  25. package/dist/logger.d.ts +3 -0
  26. package/dist/logger.d.ts.map +1 -0
  27. package/dist/logger.js +10 -0
  28. package/dist/logger.js.map +1 -0
  29. package/dist/parse-json-response.d.ts +6 -0
  30. package/dist/parse-json-response.d.ts.map +1 -0
  31. package/dist/parse-json-response.js +52 -0
  32. package/dist/parse-json-response.js.map +1 -0
  33. package/dist/skills/cdp-utils.d.ts +4 -0
  34. package/dist/skills/cdp-utils.d.ts.map +1 -0
  35. package/dist/skills/cdp-utils.js +25 -0
  36. package/dist/skills/cdp-utils.js.map +1 -0
  37. package/dist/skills/cloudflare-checkbox.d.ts +3 -0
  38. package/dist/skills/cloudflare-checkbox.d.ts.map +1 -0
  39. package/dist/skills/cloudflare-checkbox.js +108 -0
  40. package/dist/skills/cloudflare-checkbox.js.map +1 -0
  41. package/dist/skills/dismiss-popup.d.ts +4 -0
  42. package/dist/skills/dismiss-popup.d.ts.map +1 -0
  43. package/dist/skills/dismiss-popup.js +147 -0
  44. package/dist/skills/dismiss-popup.js.map +1 -0
  45. package/dist/skills/infinite-scroll.d.ts +32 -0
  46. package/dist/skills/infinite-scroll.d.ts.map +1 -0
  47. package/dist/skills/infinite-scroll.js +92 -0
  48. package/dist/skills/infinite-scroll.js.map +1 -0
  49. package/dist/skills/loop-detection.d.ts +16 -0
  50. package/dist/skills/loop-detection.d.ts.map +1 -0
  51. package/dist/skills/loop-detection.js +35 -0
  52. package/dist/skills/loop-detection.js.map +1 -0
  53. package/dist/skills/page-state.d.ts +11 -0
  54. package/dist/skills/page-state.d.ts.map +1 -0
  55. package/dist/skills/page-state.js +55 -0
  56. package/dist/skills/page-state.js.map +1 -0
  57. package/dist/skills/press-and-hold.d.ts +10 -0
  58. package/dist/skills/press-and-hold.d.ts.map +1 -0
  59. package/dist/skills/press-and-hold.js +183 -0
  60. package/dist/skills/press-and-hold.js.map +1 -0
  61. package/dist/skills/recovery.d.ts +20 -0
  62. package/dist/skills/recovery.d.ts.map +1 -0
  63. package/dist/skills/recovery.js +215 -0
  64. package/dist/skills/recovery.js.map +1 -0
  65. package/dist/skills/tab-manager.d.ts +18 -0
  66. package/dist/skills/tab-manager.d.ts.map +1 -0
  67. package/dist/skills/tab-manager.js +50 -0
  68. package/dist/skills/tab-manager.js.map +1 -0
  69. package/dist/types.d.ts +148 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +18 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/web-search.d.ts +2 -0
  74. package/dist/web-search.d.ts.map +1 -0
  75. package/dist/web-search.js +77 -0
  76. package/dist/web-search.js.map +1 -0
  77. package/package.json +60 -0
@@ -0,0 +1,108 @@
1
+ import { getCdpBaseUrl } from './cdp-utils.js';
2
+ import { isStillBlocked } from './press-and-hold.js';
3
+ import { logger } from '../logger.js';
4
+ const MAX_WAIT_MS = 15_000;
5
+ const POLL_INTERVAL_MS = 1_000;
6
+ async function findCloudflareTarget(page) {
7
+ const baseUrl = getCdpBaseUrl(page);
8
+ const res = await fetch(baseUrl + '/json');
9
+ const targets = (await res.json());
10
+ const cfTarget = targets.find((t) => {
11
+ if (t.type !== 'iframe')
12
+ return false;
13
+ try {
14
+ const host = new URL(t.url).hostname;
15
+ return host === 'challenges.cloudflare.com' || host.endsWith('.cloudflare.com') || t.url.includes('turnstile');
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ });
21
+ if (cfTarget) {
22
+ logger.info({ targetId: cfTarget.id, url: cfTarget.url.substring(0, 100) }, 'cloudflare: found turnstile target via CDP');
23
+ return cfTarget.id;
24
+ }
25
+ logger.info({ targetCount: targets.length }, 'cloudflare: no turnstile target in CDP');
26
+ return null;
27
+ }
28
+ async function getCheckboxPosition(page) {
29
+ // The Cloudflare iframe isn't visible via document.querySelectorAll('iframe')
30
+ // but its container div IS in the DOM. Find it by looking for the Turnstile widget wrapper.
31
+ const result = (await page.evaluate(`
32
+ (function() {
33
+ // Turnstile renders inside a div with specific attributes
34
+ var candidates = document.querySelectorAll('div[id^="cf-"], div[class*="cf-turnstile"], div[data-sitekey]');
35
+ for (var i = 0; i < candidates.length; i++) {
36
+ var rect = candidates[i].getBoundingClientRect();
37
+ if (rect.width > 20 && rect.height > 20) {
38
+ return JSON.stringify({ source: 'turnstile-div', x: Math.round(rect.left + 25), y: Math.round(rect.top + rect.height / 2), w: Math.round(rect.width), h: Math.round(rect.height) });
39
+ }
40
+ }
41
+
42
+ // Look for any element containing the checkbox visually — check all divs near "not a bot" text
43
+ var pattern = /not a bot|verify you are human/i;
44
+ var all = document.querySelectorAll('p, span, div');
45
+ for (var j = 0; j < all.length; j++) {
46
+ var t = (all[j].textContent || '').trim();
47
+ if (pattern.test(t) && t.length < 200) {
48
+ var el = all[j];
49
+ var rect = el.getBoundingClientRect();
50
+ if (rect.width > 50 && rect.height > 10 && rect.height < 80) {
51
+ // The checkbox is typically right below or at the same level
52
+ var sibling = el.nextElementSibling;
53
+ while (sibling) {
54
+ var sRect = sibling.getBoundingClientRect();
55
+ if (sRect.width > 20 && sRect.height > 20 && sRect.height < 100) {
56
+ return JSON.stringify({ source: 'sibling', tag: sibling.tagName, x: Math.round(sRect.left + 25), y: Math.round(sRect.top + sRect.height / 2), w: Math.round(sRect.width), h: Math.round(sRect.height) });
57
+ }
58
+ sibling = sibling.nextElementSibling;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ return null;
65
+ })()
66
+ `));
67
+ if (result === null)
68
+ return null;
69
+ const parsed = JSON.parse(result);
70
+ logger.info(parsed, 'cloudflare: found checkbox position');
71
+ return { x: parsed.x, y: parsed.y };
72
+ }
73
+ export async function clickCloudflareCheckbox(page) {
74
+ try {
75
+ logger.info('cloudflare: starting');
76
+ // First verify the Cloudflare target exists via CDP
77
+ const targetId = await findCloudflareTarget(page);
78
+ if (targetId === null) {
79
+ logger.info('cloudflare: no Cloudflare target found');
80
+ return false;
81
+ }
82
+ // Find the checkbox position
83
+ const coords = await getCheckboxPosition(page);
84
+ if (!coords) {
85
+ logger.info('cloudflare: could not determine checkbox position');
86
+ return false;
87
+ }
88
+ logger.info({ x: coords.x, y: coords.y }, 'cloudflare: clicking');
89
+ await page.mouseClick(coords.x, coords.y, { delayMs: 200 });
90
+ logger.info('cloudflare: clicked');
91
+ const start = Date.now();
92
+ while (Date.now() - start < MAX_WAIT_MS) {
93
+ await page.waitFor({ timeMs: POLL_INTERVAL_MS });
94
+ const stillBlocked = await isStillBlocked(page, 'cloudflare_checkbox');
95
+ if (!stillBlocked) {
96
+ logger.info('cloudflare: verification passed');
97
+ return true;
98
+ }
99
+ }
100
+ logger.info('cloudflare: still blocked after waiting');
101
+ return false;
102
+ }
103
+ catch (err) {
104
+ logger.error({ err: err instanceof Error ? err.message : err }, 'cloudflare: failed');
105
+ return false;
106
+ }
107
+ }
108
+ //# sourceMappingURL=cloudflare-checkbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare-checkbox.js","sourceRoot":"","sources":["../../src/skills/cloudflare-checkbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { CrawlPage } from 'browserclaw';
2
+ export declare function detectPopup(page: CrawlPage): Promise<boolean>;
3
+ export declare function dismissPopup(page: CrawlPage): Promise<boolean>;
4
+ //# sourceMappingURL=dismiss-popup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismiss-popup.d.ts","sourceRoot":"","sources":["../../src/skills/dismiss-popup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBnE;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAyHpE"}
@@ -0,0 +1,147 @@
1
+ import { logger } from '../logger.js';
2
+ export async function detectPopup(page) {
3
+ return (await page.evaluate(`
4
+ (function() {
5
+ function isActuallyVisible(el) {
6
+ if (typeof el.checkVisibility === 'function') {
7
+ if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false;
8
+ }
9
+ if (el.getAttribute('aria-hidden') === 'true') return false;
10
+ var style = window.getComputedStyle(el);
11
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
12
+ if (parseFloat(style.opacity) < 0.1) return false;
13
+ if (style.pointerEvents === 'none' && parseFloat(style.opacity) < 0.5) return false;
14
+ var rect = el.getBoundingClientRect();
15
+ if (rect.width <= 100 || rect.height <= 50) return false;
16
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return false;
17
+ return true;
18
+ }
19
+
20
+ var all = document.querySelectorAll('[role="dialog"], [role="alertdialog"], [class*="modal"], [class*="popup"], [class*="overlay"], [class*="banner"], [class*="consent"], form[action*="consent"]');
21
+ for (var i = 0; i < all.length; i++) {
22
+ if (isActuallyVisible(all[i])) return true;
23
+ }
24
+ return false;
25
+ })()
26
+ `));
27
+ }
28
+ export async function dismissPopup(page) {
29
+ try {
30
+ const dismissed = (await page.evaluate(`
31
+ (function() {
32
+ function isBtnVisible(el) {
33
+ if (typeof el.checkVisibility === 'function') {
34
+ if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false;
35
+ }
36
+ if (el.getAttribute('aria-hidden') === 'true') return false;
37
+ var style = window.getComputedStyle(el);
38
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
39
+ if (parseFloat(style.opacity) < 0.1) return false;
40
+ var rect = el.getBoundingClientRect();
41
+ if (rect.width === 0 || rect.height === 0) return false;
42
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return false;
43
+ return true;
44
+ }
45
+
46
+ function isModalVisible(el) {
47
+ if (typeof el.checkVisibility === 'function') {
48
+ if (!el.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) return false;
49
+ }
50
+ if (el.getAttribute('aria-hidden') === 'true') return false;
51
+ var style = window.getComputedStyle(el);
52
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
53
+ if (parseFloat(style.opacity) < 0.1) return false;
54
+ var rect = el.getBoundingClientRect();
55
+ if (rect.width <= 100 || rect.height <= 50) return false;
56
+ if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return false;
57
+ return true;
58
+ }
59
+
60
+ var closePatterns = [
61
+ '[aria-label="Close"]',
62
+ '[aria-label="close"]',
63
+ '[aria-label="Dismiss"]',
64
+ '[aria-label="dismiss"]',
65
+ 'button[class*="close"]',
66
+ 'button[class*="Close"]',
67
+ 'button[class*="dismiss"]',
68
+ '[class*="close-button"]',
69
+ '[class*="closeButton"]',
70
+ '[class*="modal-close"]',
71
+ '[class*="popup-close"]',
72
+ '[class*="banner-close"]',
73
+ '[data-dismiss]',
74
+ '[data-testid="dialog-close"]',
75
+ '[data-testid="modal-close"]',
76
+ ];
77
+
78
+ for (var i = 0; i < closePatterns.length; i++) {
79
+ var btns = document.querySelectorAll(closePatterns[i]);
80
+ for (var j = 0; j < btns.length; j++) {
81
+ if (isBtnVisible(btns[j])) {
82
+ btns[j].click();
83
+ return true;
84
+ }
85
+ }
86
+ }
87
+
88
+ var dismissPattern = /^(no|not|skip|close|dismiss|got it|later|cancel|x|✕|✖|×|continue|don.t|decline|reject|deny|nah|pass|ignore|maybe|never|nope|accept|agree)\\b|\\b(thanks|thank|no thanks|not now|not interested|maybe later|skip this|close this|hide|opt out|accept all|reject all|i agree)$/i;
89
+ var modals = document.querySelectorAll('[role="dialog"], [role="alertdialog"], [class*="modal"], [class*="popup"], [class*="overlay"]');
90
+ var visibleModal = null;
91
+ for (var mi = modals.length - 1; mi >= 0; mi--) {
92
+ if (isModalVisible(modals[mi])) { visibleModal = modals[mi]; break; }
93
+ }
94
+ var searchRoot = visibleModal || document.body;
95
+ var clickables = searchRoot.querySelectorAll('button, a, [role="button"], [onclick], span[class*="close"], div[class*="close"]');
96
+ for (var k = 0; k < clickables.length; k++) {
97
+ var b = clickables[k];
98
+ var text = (b.textContent || '').trim();
99
+ if (text.length > 30) continue;
100
+ if (!isBtnVisible(b)) continue;
101
+ if (text.length <= 2 || dismissPattern.test(text)) {
102
+ b.click();
103
+ return true;
104
+ }
105
+ }
106
+
107
+ var consent = document.querySelector('form[action*="consent"], [class*="consent"], [id*="consent"], [id*="cookie"]');
108
+ if (consent) {
109
+ var consentBtns = consent.querySelectorAll('button');
110
+ for (var m = consentBtns.length - 1; m >= 0; m--) {
111
+ var cb = consentBtns[m];
112
+ if (!isBtnVisible(cb)) continue;
113
+ var cbRect = cb.getBoundingClientRect();
114
+ if (cbRect.width > 50 && cbRect.height > 20) {
115
+ cb.click();
116
+ return true;
117
+ }
118
+ }
119
+ }
120
+
121
+ return false;
122
+ })()
123
+ `));
124
+ if (dismissed) {
125
+ logger.info('dismiss-popup: closed a popup via click');
126
+ await page.waitFor({ timeMs: 500 });
127
+ return true;
128
+ }
129
+ // Fallback: press Escape to dismiss popups/date pickers/overlays that have no close button
130
+ logger.info('dismiss-popup: no close button found, trying Escape key');
131
+ await page.press('Escape');
132
+ await page.waitFor({ timeMs: 500 });
133
+ // Check if the popup was dismissed
134
+ const stillOpen = await detectPopup(page);
135
+ if (!stillOpen) {
136
+ logger.info('dismiss-popup: closed popup via Escape key');
137
+ return true;
138
+ }
139
+ logger.info('dismiss-popup: Escape did not close the popup');
140
+ return false;
141
+ }
142
+ catch (err) {
143
+ logger.error({ err }, 'dismiss-popup failed');
144
+ return false;
145
+ }
146
+ }
147
+ //# sourceMappingURL=dismiss-popup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dismiss-popup.js","sourceRoot":"","sources":["../../src/skills/dismiss-popup.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAe;IAC/C,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;GAuB3B,CAAC,CAAY,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAe;IAChD,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6FtC,CAAC,CAAY,CAAC;QAEf,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;YACvD,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,2FAA2F;QAC3F,MAAM,CAAC,IAAI,CAAC,yDAAyD,CAAC,CAAC;QACvE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC3B,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAEpC,mCAAmC;QACnC,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;YAC1D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,sBAAsB,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { CrawlPage } from 'browserclaw';
2
+ export interface InfiniteScrollSnapshot {
3
+ scrollTop: number;
4
+ scrollHeight: number;
5
+ clientHeight: number;
6
+ visibleText: string;
7
+ lastItemText: string;
8
+ itemCount: number;
9
+ url: string;
10
+ }
11
+ export interface InfiniteScrollOptions {
12
+ containerSelector?: string;
13
+ itemSplitPattern?: string;
14
+ stepDelayMs?: number;
15
+ boundaryPushPx?: number;
16
+ maxRounds?: number;
17
+ }
18
+ export interface InfiniteScrollRound {
19
+ round: number;
20
+ before: InfiniteScrollSnapshot;
21
+ after: InfiniteScrollSnapshot;
22
+ changed: boolean;
23
+ }
24
+ export interface InfiniteScrollResult {
25
+ ok: boolean;
26
+ selector: string;
27
+ rounds: InfiniteScrollRound[];
28
+ advanced: boolean;
29
+ reason: string;
30
+ }
31
+ export declare function advanceInfiniteScroll(page: CrawlPage, opts?: InfiniteScrollOptions): Promise<InfiniteScrollResult>;
32
+ //# sourceMappingURL=infinite-scroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infinite-scroll.d.ts","sourceRoot":"","sources":["../../src/skills/infinite-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,sBAAsB,CAAC;IAC/B,KAAK,EAAE,sBAAsB,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAyDD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,SAAS,EACf,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAkD/B"}
@@ -0,0 +1,92 @@
1
+ import { logger } from '../logger.js';
2
+ const DEFAULT_CONTAINER_SELECTOR = 'main#workspace';
3
+ const DEFAULT_ITEM_SPLIT_PATTERN = 'Feed po.t|Feed post';
4
+ const DEFAULT_STEP_DELAY_MS = 2500;
5
+ const DEFAULT_BOUNDARY_PUSH_PX = 5000;
6
+ const DEFAULT_MAX_ROUNDS = 3;
7
+ async function wait(ms) {
8
+ await new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
10
+ async function captureSnapshot(page, selector, itemSplitPattern) {
11
+ return (await page.evaluate(`(() => {
12
+ const selector = ${JSON.stringify(selector)};
13
+ const itemSplitPattern = ${JSON.stringify(itemSplitPattern)};
14
+ const text = (value) => typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '';
15
+ const container = document.querySelector(selector);
16
+ if (!container) {
17
+ throw new Error('infinite_scroll_container_not_found:' + selector);
18
+ }
19
+
20
+ const visibleText = text(container.innerText || container.textContent || '');
21
+ const splitRe = new RegExp(itemSplitPattern, 'i');
22
+ const parts = visibleText
23
+ .split(splitRe)
24
+ .map((part) => part.trim())
25
+ .filter(Boolean);
26
+
27
+ return {
28
+ scrollTop: container.scrollTop,
29
+ scrollHeight: container.scrollHeight,
30
+ clientHeight: container.clientHeight,
31
+ visibleText: visibleText.slice(0, 4000),
32
+ lastItemText: (parts.length ? parts[parts.length - 1] : visibleText).slice(0, 1600),
33
+ itemCount: parts.length,
34
+ url: window.location.href,
35
+ };
36
+ })()`));
37
+ }
38
+ async function pushPastBottom(page, selector, boundaryPushPx) {
39
+ await page.evaluate(`(() => {
40
+ const selector = ${JSON.stringify(selector)};
41
+ const boundaryPushPx = ${JSON.stringify(boundaryPushPx)};
42
+ const container = document.querySelector(selector);
43
+ if (!container) {
44
+ throw new Error('infinite_scroll_container_not_found:' + selector);
45
+ }
46
+ container.scrollTop = container.scrollHeight + boundaryPushPx;
47
+ })()`);
48
+ }
49
+ export async function advanceInfiniteScroll(page, opts = {}) {
50
+ const selector = opts.containerSelector ?? DEFAULT_CONTAINER_SELECTOR;
51
+ const itemSplitPattern = opts.itemSplitPattern ?? DEFAULT_ITEM_SPLIT_PATTERN;
52
+ const stepDelayMs = opts.stepDelayMs ?? DEFAULT_STEP_DELAY_MS;
53
+ const boundaryPushPx = opts.boundaryPushPx ?? DEFAULT_BOUNDARY_PUSH_PX;
54
+ const maxRounds = opts.maxRounds ?? DEFAULT_MAX_ROUNDS;
55
+ const rounds = [];
56
+ for (let round = 1; round <= maxRounds; round += 1) {
57
+ const before = await captureSnapshot(page, selector, itemSplitPattern);
58
+ await pushPastBottom(page, selector, boundaryPushPx);
59
+ await wait(stepDelayMs);
60
+ const after = await captureSnapshot(page, selector, itemSplitPattern);
61
+ const changed = before.lastItemText !== after.lastItemText;
62
+ rounds.push({ round, before, after, changed });
63
+ logger.info({
64
+ selector,
65
+ round,
66
+ changed,
67
+ beforeScrollTop: before.scrollTop,
68
+ afterScrollTop: after.scrollTop,
69
+ beforeScrollHeight: before.scrollHeight,
70
+ afterScrollHeight: after.scrollHeight,
71
+ beforeLastItem: before.lastItemText.slice(0, 160),
72
+ afterLastItem: after.lastItemText.slice(0, 160),
73
+ }, 'infinite-scroll round result');
74
+ if (changed) {
75
+ return {
76
+ ok: true,
77
+ selector,
78
+ rounds,
79
+ advanced: true,
80
+ reason: 'last_visible_item_changed',
81
+ };
82
+ }
83
+ }
84
+ return {
85
+ ok: false,
86
+ selector,
87
+ rounds,
88
+ advanced: false,
89
+ reason: 'last_visible_item_unchanged',
90
+ };
91
+ }
92
+ //# sourceMappingURL=infinite-scroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infinite-scroll.js","sourceRoot":"","sources":["../../src/skills/infinite-scroll.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAmCtC,MAAM,0BAA0B,GAAG,gBAAgB,CAAC;AACpD,MAAM,0BAA0B,GAAG,qBAAqB,CAAC;AACzD,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AACtC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,KAAK,UAAU,IAAI,CAAC,EAAU;IAC5B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,IAAe,EACf,QAAgB,EAChB,gBAAwB;IAExB,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC;yBACL,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;iCAChB,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;;;;;SAuBxD,CAAC,CAA2B,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAAe,EAAE,QAAgB,EAAE,cAAsB;IACrF,MAAM,IAAI,CAAC,QAAQ,CAAC;yBACG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;+BAClB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC;;;;;;SAMpD,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,IAAe,EACf,OAA8B,EAAE;IAEhC,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,IAAI,0BAA0B,CAAC;IACtE,MAAM,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,IAAI,0BAA0B,CAAC;IAC7E,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,qBAAqB,CAAC;IAC9D,MAAM,cAAc,GAAG,IAAI,CAAC,cAAc,IAAI,wBAAwB,CAAC;IACvE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,MAAM,GAA0B,EAAE,CAAC;IAEzC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,SAAS,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QACvE,MAAM,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,WAAW,CAAC,CAAC;QACxB,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,MAAM,CAAC,YAAY,KAAK,KAAK,CAAC,YAAY,CAAC;QAE3D,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAE/C,MAAM,CAAC,IAAI,CACT;YACE,QAAQ;YACR,KAAK;YACL,OAAO;YACP,eAAe,EAAE,MAAM,CAAC,SAAS;YACjC,cAAc,EAAE,KAAK,CAAC,SAAS;YAC/B,kBAAkB,EAAE,MAAM,CAAC,YAAY;YACvC,iBAAiB,EAAE,KAAK,CAAC,YAAY;YACrC,cAAc,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YACjD,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;SAChD,EACD,8BAA8B,CAC/B,CAAC;QAEF,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,QAAQ;gBACR,MAAM;gBACN,QAAQ,EAAE,IAAI;gBACd,MAAM,EAAE,2BAA2B;aACpC,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,KAAK;QACT,QAAQ;QACR,MAAM;QACN,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,6BAA6B;KACtC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { AgentStep } from '../types.js';
2
+ interface LoopNudge {
3
+ level: 'gentle' | 'warning' | 'urgent';
4
+ message: string;
5
+ }
6
+ /**
7
+ * Detects exact action repetition (same action + same ref) with escalating severity.
8
+ * Pattern-based stuck diagnosis (stagnation, alternating failures, etc.) is handled
9
+ * by recovery.ts which provides richer diagnostics.
10
+ */
11
+ export declare function detectLoop(action: {
12
+ action: string;
13
+ ref?: string;
14
+ }, history: AgentStep[]): LoopNudge | null;
15
+ export {};
16
+ //# sourceMappingURL=loop-detection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-detection.d.ts","sourceRoot":"","sources":["../../src/skills/loop-detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,UAAU,SAAS;IACjB,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,SAAS,GAAG,IAAI,CAmC3G"}
@@ -0,0 +1,35 @@
1
+ const LOOP_WINDOW = 20;
2
+ /**
3
+ * Detects exact action repetition (same action + same ref) with escalating severity.
4
+ * Pattern-based stuck diagnosis (stagnation, alternating failures, etc.) is handled
5
+ * by recovery.ts which provides richer diagnostics.
6
+ */
7
+ export function detectLoop(action, history) {
8
+ if (history.length < 5)
9
+ return null;
10
+ const actionKey = `${action.action}:${action.ref ?? ''}`;
11
+ const window = history.slice(-LOOP_WINDOW);
12
+ const windowKeys = window.map((h) => `${h.action.action}:${h.action.ref ?? ''}`);
13
+ // Count how many times this exact action appears in the window
14
+ const repetitions = windowKeys.filter((k) => k === actionKey).length;
15
+ if (repetitions >= 12) {
16
+ return {
17
+ level: 'urgent',
18
+ message: 'STUCK: You have repeated this exact action 12+ times. This approach is not working. You MUST try something completely different. If a popup, date picker, or overlay is blocking the UI, use "keyboard" with "Escape" to close it. Otherwise try a different element, a different page, or a different strategy entirely.',
19
+ };
20
+ }
21
+ if (repetitions >= 8) {
22
+ return {
23
+ level: 'warning',
24
+ message: 'You have repeated this action 8+ times. Consider whether this approach is making progress. If not, try pressing Escape to dismiss any blocking popups or overlays, then try a different path — use site navigation, search, or a different element.',
25
+ };
26
+ }
27
+ if (repetitions >= 5) {
28
+ return {
29
+ level: 'gentle',
30
+ message: 'You have repeated this action several times. If you are making progress with each repetition, keep going. If not, try a different approach: use "keyboard" with "Escape" to close any blocking popups, date pickers, or overlays, then try a different element or strategy.',
31
+ };
32
+ }
33
+ return null;
34
+ }
35
+ //# sourceMappingURL=loop-detection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop-detection.js","sourceRoot":"","sources":["../../src/skills/loop-detection.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAG,EAAE,CAAC;AAOvB;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,MAAwC,EAAE,OAAoB;IACvF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,SAAS,GAAG,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,CAAC;IAEjF,+DAA+D;IAC/D,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IAErE,IAAI,WAAW,IAAI,EAAE,EAAE,CAAC;QACtB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,OAAO,EACL,2TAA2T;SAC9T,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,OAAO,EACL,qPAAqP;SACxP,CAAC;IACJ,CAAC;IAED,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,OAAO,EACL,6QAA6Q;SAChR,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,11 @@
1
+ export type PageState = 'login' | 'loading' | 'error' | 'anti_bot' | 'empty' | 'results' | 'detail' | 'unknown';
2
+ export interface DetectPageStateInput {
3
+ snapshot: string;
4
+ domText: string;
5
+ title: string;
6
+ url: string;
7
+ antiBotType: string | null;
8
+ }
9
+ export declare function detectPageState(input: DetectPageStateInput): PageState;
10
+ export declare function shouldBlockDone(state: PageState, historyLength: number, answer?: string): string | null;
11
+ //# sourceMappingURL=page-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-state.d.ts","sourceRoot":"","sources":["../../src/skills/page-state.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAMD,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,SAAS,CAgDtE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAevG"}
@@ -0,0 +1,55 @@
1
+ function hasAny(text, patterns) {
2
+ return patterns.some((p) => p.test(text));
3
+ }
4
+ export function detectPageState(input) {
5
+ const snapshot = input.snapshot || '';
6
+ const domText = input.domText || '';
7
+ const title = input.title || '';
8
+ const url = input.url || '';
9
+ const joined = `${title}\n${url}\n${snapshot}\n${domText}`;
10
+ if (input.antiBotType !== null)
11
+ return 'anti_bot';
12
+ if (hasAny(joined, [/loading/i, /please wait/i, /skeleton/i, /spinner/i])) {
13
+ return 'loading';
14
+ }
15
+ if (hasAny(joined, [/404/i, /500/i, /error/i, /something went wrong/i, /access denied/i, /forbidden/i])) {
16
+ return 'error';
17
+ }
18
+ if (hasAny(joined, [/sign in/i, /log in/i, /login/i, /forgot your password/i, /keep me logged in/i, /create account/i])) {
19
+ return 'login';
20
+ }
21
+ if (hasAny(joined, [/no results/i, /0 results/i, /nothing found/i, /no events/i, /no listings/i])) {
22
+ return 'empty';
23
+ }
24
+ if (hasAny(joined, [/details/i, /description/i, /about this/i, /overview/i, /specifications/i])) {
25
+ return 'detail';
26
+ }
27
+ if (hasAny(joined, [
28
+ /results/i,
29
+ /event/i,
30
+ /listing/i,
31
+ /show_time/i,
32
+ /activities/i,
33
+ /search results/i,
34
+ /\/[a-z0-9-]*results/i,
35
+ ])) {
36
+ return 'results';
37
+ }
38
+ if (snapshot.trim() === '' && domText.trim() === '')
39
+ return 'empty';
40
+ return 'unknown';
41
+ }
42
+ export function shouldBlockDone(state, historyLength, answer) {
43
+ const answerText = (answer ?? '').trim();
44
+ if (historyLength < 2 && answerText.length < 40) {
45
+ return 'You are trying to finish too early. Verify the page and gather at least one concrete result before using done.';
46
+ }
47
+ if (state === 'login' || state === 'loading' || state === 'anti_bot') {
48
+ return `The current page state is ${state}. Do not use done yet — resolve the page state or gather verified results first.`;
49
+ }
50
+ if ((state === 'results' || state === 'detail') && answerText.length < 40) {
51
+ return 'Your answer is too thin for the current page. Extract concrete findings before using done.';
52
+ }
53
+ return null;
54
+ }
55
+ //# sourceMappingURL=page-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-state.js","sourceRoot":"","sources":["../../src/skills/page-state.ts"],"names":[],"mappings":"AAUA,SAAS,MAAM,CAAC,IAAY,EAAE,QAAkB;IAC9C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;IACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IAChC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,GAAG,KAAK,KAAK,GAAG,KAAK,QAAQ,KAAK,OAAO,EAAE,CAAC;IAE3D,IAAI,KAAK,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,UAAU,CAAC;IAElD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;QAC1E,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;QACxG,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IACE,MAAM,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,iBAAiB,CAAC,CAAC,EACnH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QAClG,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IACE,MAAM,CAAC,MAAM,EAAE;QACb,UAAU;QACV,QAAQ;QACR,UAAU;QACV,YAAY;QACZ,aAAa;QACb,iBAAiB;QACjB,sBAAsB;KACvB,CAAC,EACF,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,OAAO,CAAC;IAEpE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAgB,EAAE,aAAqB,EAAE,MAAe;IACtF,MAAM,UAAU,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,IAAI,aAAa,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAChD,OAAO,gHAAgH,CAAC;IAC1H,CAAC;IAED,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;QACrE,OAAO,6BAA6B,KAAK,kFAAkF,CAAC;IAC9H,CAAC;IAED,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC1E,OAAO,4FAA4F,CAAC;IACtG,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { CrawlPage } from 'browserclaw';
2
+ export type AntiBotType = 'press_and_hold' | 'cloudflare_checkbox' | null;
3
+ export declare function getPageText(page: CrawlPage): Promise<string>;
4
+ export declare function isStillBlocked(page: CrawlPage, type: AntiBotType): Promise<boolean>;
5
+ export declare function detectAntiBot(domText: string): AntiBotType;
6
+ export declare function enrichSnapshot(snapshot: string, domText: string, type: AntiBotType): string;
7
+ export declare function pressAndHold(page: CrawlPage, opts?: {
8
+ holdMs?: number;
9
+ }): Promise<boolean>;
10
+ //# sourceMappingURL=press-and-hold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"press-and-hold.d.ts","sourceRoot":"","sources":["../../src/skills/press-and-hold.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,qBAAqB,GAAG,IAAI,CAAC;AAoH1E,wBAAsB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAelE;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAOzF;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAkB1D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,CAc3F;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAqChG"}