@dyyz1993/agent-browser 0.24.0 → 0.26.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 (245) hide show
  1. package/bin/agent-browser-darwin-arm64 +0 -0
  2. package/dist/__tests__/e2e/utils/test-helpers.d.ts +2 -2
  3. package/dist/__tests__/e2e/utils/test-helpers.d.ts.map +1 -1
  4. package/dist/__tests__/e2e/utils/test-helpers.js +6 -4
  5. package/dist/__tests__/e2e/utils/test-helpers.js.map +1 -1
  6. package/dist/actions/advanced.d.ts +73 -0
  7. package/dist/actions/advanced.d.ts.map +1 -0
  8. package/dist/actions/advanced.js +390 -0
  9. package/dist/actions/advanced.js.map +1 -0
  10. package/dist/actions/context.d.ts +36 -0
  11. package/dist/actions/context.d.ts.map +1 -0
  12. package/dist/actions/context.js +164 -0
  13. package/dist/actions/context.js.map +1 -0
  14. package/dist/actions/crawl.d.ts +8 -0
  15. package/dist/actions/crawl.d.ts.map +1 -0
  16. package/dist/actions/crawl.js +290 -0
  17. package/dist/actions/crawl.js.map +1 -0
  18. package/dist/actions/elements.d.ts +11 -0
  19. package/dist/actions/elements.d.ts.map +1 -0
  20. package/dist/actions/elements.js +78 -0
  21. package/dist/actions/elements.js.map +1 -0
  22. package/dist/actions/flow.d.ts +4 -0
  23. package/dist/actions/flow.d.ts.map +1 -0
  24. package/dist/actions/flow.js +170 -0
  25. package/dist/actions/flow.js.map +1 -0
  26. package/dist/actions/index.d.ts +7 -0
  27. package/dist/actions/index.d.ts.map +1 -0
  28. package/dist/actions/index.js +323 -0
  29. package/dist/actions/index.js.map +1 -0
  30. package/dist/actions/interact.d.ts +4 -0
  31. package/dist/actions/interact.d.ts.map +1 -0
  32. package/dist/actions/interact.js +162 -0
  33. package/dist/actions/interact.js.map +1 -0
  34. package/dist/actions/interaction.d.ts +31 -0
  35. package/dist/actions/interaction.d.ts.map +1 -0
  36. package/dist/actions/interaction.js +477 -0
  37. package/dist/actions/interaction.js.map +1 -0
  38. package/dist/actions/locators.d.ts +14 -0
  39. package/dist/actions/locators.d.ts.map +1 -0
  40. package/dist/actions/locators.js +310 -0
  41. package/dist/actions/locators.js.map +1 -0
  42. package/dist/actions/map.d.ts +4 -0
  43. package/dist/actions/map.d.ts.map +1 -0
  44. package/dist/actions/map.js +79 -0
  45. package/dist/actions/map.js.map +1 -0
  46. package/dist/actions/meta.d.ts +44 -0
  47. package/dist/actions/meta.d.ts.map +1 -0
  48. package/dist/actions/meta.js +190 -0
  49. package/dist/actions/meta.js.map +1 -0
  50. package/dist/actions/mouse.d.ts +8 -0
  51. package/dist/actions/mouse.d.ts.map +1 -0
  52. package/dist/actions/mouse.js +52 -0
  53. package/dist/actions/mouse.js.map +1 -0
  54. package/dist/actions/recorder.d.ts +20 -0
  55. package/dist/actions/recorder.d.ts.map +1 -0
  56. package/dist/actions/recorder.js +231 -0
  57. package/dist/actions/recorder.js.map +1 -0
  58. package/dist/actions/recording.d.ts +6 -0
  59. package/dist/actions/recording.d.ts.map +1 -0
  60. package/dist/actions/recording.js +22 -0
  61. package/dist/actions/recording.js.map +1 -0
  62. package/dist/actions/scrape.d.ts +10 -0
  63. package/dist/actions/scrape.d.ts.map +1 -0
  64. package/dist/actions/scrape.js +39 -0
  65. package/dist/actions/scrape.js.map +1 -0
  66. package/dist/actions/screencast.d.ts +8 -0
  67. package/dist/actions/screencast.d.ts.map +1 -0
  68. package/dist/actions/screencast.js +56 -0
  69. package/dist/actions/screencast.js.map +1 -0
  70. package/dist/actions/search.d.ts +4 -0
  71. package/dist/actions/search.d.ts.map +1 -0
  72. package/dist/actions/search.js +129 -0
  73. package/dist/actions/search.js.map +1 -0
  74. package/dist/actions/storage.d.ts +14 -0
  75. package/dist/actions/storage.d.ts.map +1 -0
  76. package/dist/actions/storage.js +63 -0
  77. package/dist/actions/storage.js.map +1 -0
  78. package/dist/actions/tabs.d.ts +16 -0
  79. package/dist/actions/tabs.d.ts.map +1 -0
  80. package/dist/actions/tabs.js +47 -0
  81. package/dist/actions/tabs.js.map +1 -0
  82. package/dist/actions/utils.d.ts +15 -0
  83. package/dist/actions/utils.d.ts.map +1 -0
  84. package/dist/actions/utils.js +234 -0
  85. package/dist/actions/utils.js.map +1 -0
  86. package/dist/browser/browser-manager.d.ts +249 -0
  87. package/dist/browser/browser-manager.d.ts.map +1 -0
  88. package/dist/browser/browser-manager.js +1251 -0
  89. package/dist/browser/browser-manager.js.map +1 -0
  90. package/dist/browser/index.d.ts +3 -0
  91. package/dist/browser/index.d.ts.map +1 -0
  92. package/dist/browser/index.js +2 -0
  93. package/dist/browser/index.js.map +1 -0
  94. package/dist/browser/network-tracker.d.ts +39 -0
  95. package/dist/browser/network-tracker.d.ts.map +1 -0
  96. package/dist/browser/network-tracker.js +287 -0
  97. package/dist/browser/network-tracker.js.map +1 -0
  98. package/dist/browser/providers.d.ts +27 -0
  99. package/dist/browser/providers.d.ts.map +1 -0
  100. package/dist/browser/providers.js +293 -0
  101. package/dist/browser/providers.js.map +1 -0
  102. package/dist/browser/recorder-manager.d.ts +69 -0
  103. package/dist/browser/recorder-manager.d.ts.map +1 -0
  104. package/dist/browser/recorder-manager.js +755 -0
  105. package/dist/browser/recorder-manager.js.map +1 -0
  106. package/dist/browser/recording-manager.d.ts +46 -0
  107. package/dist/browser/recording-manager.d.ts.map +1 -0
  108. package/dist/browser/recording-manager.js +156 -0
  109. package/dist/browser/recording-manager.js.map +1 -0
  110. package/dist/browser/screencast-manager.d.ts +49 -0
  111. package/dist/browser/screencast-manager.d.ts.map +1 -0
  112. package/dist/browser/screencast-manager.js +131 -0
  113. package/dist/browser/screencast-manager.js.map +1 -0
  114. package/dist/browser/types.d.ts +101 -0
  115. package/dist/browser/types.d.ts.map +1 -0
  116. package/dist/browser/types.js +2 -0
  117. package/dist/browser/types.js.map +1 -0
  118. package/dist/browser-events.d.ts +25 -0
  119. package/dist/browser-events.d.ts.map +1 -0
  120. package/dist/browser-events.js +15 -0
  121. package/dist/browser-events.js.map +1 -0
  122. package/dist/cli/commands.d.ts.map +1 -1
  123. package/dist/cli/commands.js +145 -1
  124. package/dist/cli/commands.js.map +1 -1
  125. package/dist/cli/connection.d.ts.map +1 -1
  126. package/dist/cli/connection.js +15 -22
  127. package/dist/cli/connection.js.map +1 -1
  128. package/dist/cli/flags.d.ts +1 -0
  129. package/dist/cli/flags.d.ts.map +1 -1
  130. package/dist/cli/flags.js +8 -0
  131. package/dist/cli/flags.js.map +1 -1
  132. package/dist/cli/help.d.ts.map +1 -1
  133. package/dist/cli/help.js +204 -4
  134. package/dist/cli/help.js.map +1 -1
  135. package/dist/cli/output.d.ts.map +1 -1
  136. package/dist/cli/output.js +72 -0
  137. package/dist/cli/output.js.map +1 -1
  138. package/dist/cli.js +149 -14
  139. package/dist/cli.js.map +1 -1
  140. package/dist/daemon.d.ts +1 -1
  141. package/dist/daemon.d.ts.map +1 -1
  142. package/dist/daemon.js +12 -13
  143. package/dist/daemon.js.map +1 -1
  144. package/dist/flow/exporters/playwright.d.ts +23 -1
  145. package/dist/flow/exporters/playwright.d.ts.map +1 -1
  146. package/dist/flow/exporters/playwright.js +333 -85
  147. package/dist/flow/exporters/playwright.js.map +1 -1
  148. package/dist/flow/exporters/python.d.ts +22 -0
  149. package/dist/flow/exporters/python.d.ts.map +1 -1
  150. package/dist/flow/exporters/python.js +325 -74
  151. package/dist/flow/exporters/python.js.map +1 -1
  152. package/dist/flow/exporters/selenium.d.ts.map +1 -1
  153. package/dist/flow/exporters/selenium.js +0 -1
  154. package/dist/flow/exporters/selenium.js.map +1 -1
  155. package/dist/flow/flow-executor.d.ts +1 -1
  156. package/dist/flow/flow-executor.d.ts.map +1 -1
  157. package/dist/flow/flow-executor.js +11 -11
  158. package/dist/flow/flow-executor.js.map +1 -1
  159. package/dist/flow/output.js.map +1 -1
  160. package/dist/flow/plugin-system.d.ts +1 -1
  161. package/dist/flow/plugin-system.d.ts.map +1 -1
  162. package/dist/flow/plugin-system.js +2 -2
  163. package/dist/flow/plugin-system.js.map +1 -1
  164. package/dist/flow/plugins/logging-plugin.js +1 -1
  165. package/dist/flow/plugins/logging-plugin.js.map +1 -1
  166. package/dist/flow/presets/console-capture.js +50 -0
  167. package/dist/flow/presets/fetch-capture.js +107 -0
  168. package/dist/flow/presets/sse-stream.js +85 -0
  169. package/dist/flow/presets/xhr-only.js +44 -0
  170. package/dist/flow/recorder-to-flow.d.ts.map +1 -1
  171. package/dist/flow/recorder-to-flow.js +1 -3
  172. package/dist/flow/recorder-to-flow.js.map +1 -1
  173. package/dist/flow/site-manager.d.ts.map +1 -1
  174. package/dist/flow/site-manager.js +6 -2
  175. package/dist/flow/site-manager.js.map +1 -1
  176. package/dist/human-mouse.d.ts +1 -1
  177. package/dist/human-mouse.d.ts.map +1 -1
  178. package/dist/human-mouse.js +2 -2
  179. package/dist/human-mouse.js.map +1 -1
  180. package/dist/protocol.d.ts.map +1 -1
  181. package/dist/protocol.js +91 -1
  182. package/dist/protocol.js.map +1 -1
  183. package/dist/rc-config.js +4 -4
  184. package/dist/rc-config.js.map +1 -1
  185. package/dist/recorder/inject.js +31 -5
  186. package/dist/snapshot.d.ts.map +1 -1
  187. package/dist/snapshot.js +3 -4
  188. package/dist/snapshot.js.map +1 -1
  189. package/dist/stream-server-standalone.d.ts +1 -1
  190. package/dist/stream-server-standalone.d.ts.map +1 -1
  191. package/dist/stream-server-standalone.js +42 -23
  192. package/dist/stream-server-standalone.js.map +1 -1
  193. package/dist/stream-server.d.ts +1 -1
  194. package/dist/stream-server.d.ts.map +1 -1
  195. package/dist/stream-server.js +26 -21
  196. package/dist/stream-server.js.map +1 -1
  197. package/dist/test-live.js +9 -3
  198. package/dist/test-live.js.map +1 -1
  199. package/dist/types.d.ts +123 -2
  200. package/dist/types.d.ts.map +1 -1
  201. package/dist/types.js.map +1 -1
  202. package/package.json +4 -3
  203. package/scripts/README.md +66 -0
  204. package/scripts/check_goods_container.js +35 -0
  205. package/scripts/check_page_content.js +36 -0
  206. package/scripts/click_applause_rate.js +30 -0
  207. package/scripts/copy-flow-presets.js +25 -0
  208. package/scripts/douyin-flow-test.sh +72 -0
  209. package/scripts/douyin-test.sh +101 -0
  210. package/scripts/explore_jd_page.js +31 -0
  211. package/scripts/extract_all_jd_data.js +80 -0
  212. package/scripts/extract_jd_product_detail.js +62 -0
  213. package/scripts/extract_jd_products_correct_links.js +78 -0
  214. package/scripts/extract_jd_products_final.js +80 -0
  215. package/scripts/extract_jd_reviews.js +48 -0
  216. package/scripts/extract_jd_seafood_final.js +78 -0
  217. package/scripts/extract_multiple_products.js +77 -0
  218. package/scripts/extract_products_no_scroll.js +68 -0
  219. package/scripts/extract_products_simple.js +68 -0
  220. package/scripts/find_applause_rate.js +26 -0
  221. package/scripts/find_jd_links.js +28 -0
  222. package/scripts/find_main_content.js +20 -0
  223. package/scripts/find_product_cards.js +38 -0
  224. package/scripts/find_root_content.js +26 -0
  225. package/scripts/find_unique_products.js +55 -0
  226. package/scripts/get_jd_product_detail.js +16 -0
  227. package/scripts/get_jd_products.js +23 -0
  228. package/scripts/get_jd_seafood_products.js +44 -0
  229. package/scripts/get_product_details_from_images.js +54 -0
  230. package/scripts/verify-form.sh +67 -0
  231. package/scripts/verify-login.sh +65 -0
  232. package/scripts/verify-recording.sh +80 -0
  233. package/scripts/verify-upload.sh +41 -0
  234. package/bin/agent-browser-darwin-x64 +0 -0
  235. package/bin/agent-browser-linux-arm64 +0 -0
  236. package/bin/agent-browser-linux-x64 +0 -0
  237. package/bin/agent-browser-win32-x64.exe +0 -0
  238. package/dist/actions.d.ts +0 -51
  239. package/dist/actions.d.ts.map +0 -1
  240. package/dist/actions.js +0 -2662
  241. package/dist/actions.js.map +0 -1
  242. package/dist/browser.d.ts +0 -651
  243. package/dist/browser.d.ts.map +0 -1
  244. package/dist/browser.js +0 -3088
  245. package/dist/browser.js.map +0 -1
@@ -0,0 +1,1251 @@
1
+ import { chromium, firefox, webkit, devices, } from 'playwright-core';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { getEnhancedSnapshot, generateStableSelectors, parseRef, } from '../snapshot.js';
5
+ import { SnapshotStore } from '../snapshot-store.js';
6
+ import { getEventCallbacks } from '../browser-events.js';
7
+ import { NetworkTracker } from './network-tracker.js';
8
+ import { ScreencastManager } from './screencast-manager.js';
9
+ import { RecordingManager } from './recording-manager.js';
10
+ import { RecorderManager } from './recorder-manager.js';
11
+ import { connectToBrowserbase, connectToKernel, connectToBrowserUse, connectViaCDP, closeBrowserbaseSession, closeBrowserUseSession, closeKernelSession, } from './providers.js';
12
+ export class BrowserManager {
13
+ browser = null;
14
+ cdpEndpoint = null;
15
+ isPersistentContext = false;
16
+ browserbaseSessionId = null;
17
+ browserbaseApiKey = null;
18
+ browserUseSessionId = null;
19
+ browserUseApiKey = null;
20
+ kernelSessionId = null;
21
+ kernelApiKey = null;
22
+ contexts = [];
23
+ pages = [];
24
+ activePageIndex = 0;
25
+ dialogHandler = null;
26
+ isRecordingHar = false;
27
+ refMap = {};
28
+ lastSnapshot = '';
29
+ snapshotStore = new SnapshotStore();
30
+ scopedHeaderRoutes = new Map();
31
+ commandHistory = [];
32
+ cdpSession = null;
33
+ routes = new Map();
34
+ network;
35
+ screencast;
36
+ recording;
37
+ recorder;
38
+ constructor() {
39
+ this.network = new NetworkTracker(() => this.getPage());
40
+ this.screencast = new ScreencastManager(() => this.getCDPSession());
41
+ this.recording = new RecordingManager({
42
+ getBrowser: () => this.browser,
43
+ getPage: () => this.getPage(),
44
+ getContexts: () => this.contexts,
45
+ getPages: () => this.pages,
46
+ getActivePageIndex: () => this.activePageIndex,
47
+ setActivePageIndex: (i) => {
48
+ this.activePageIndex = i;
49
+ },
50
+ addPage: (page) => {
51
+ if (!this.pages.includes(page))
52
+ this.pages.push(page);
53
+ },
54
+ addContext: (context) => {
55
+ if (!this.contexts.includes(context))
56
+ this.contexts.push(context);
57
+ },
58
+ removePage: (page) => {
59
+ const idx = this.pages.indexOf(page);
60
+ if (idx !== -1)
61
+ this.pages.splice(idx, 1);
62
+ },
63
+ removeContext: (context) => {
64
+ const idx = this.contexts.indexOf(context);
65
+ if (idx !== -1)
66
+ this.contexts.splice(idx, 1);
67
+ },
68
+ setupPageTracking: (page) => this.setupPageTracking(page),
69
+ invalidateCDPSession: () => this.invalidateCDPSession(),
70
+ });
71
+ this.recorder = new RecorderManager({
72
+ getPage: () => this.getPage(),
73
+ getPages: () => this.pages,
74
+ getActivePageIndex: () => this.activePageIndex,
75
+ setActivePageIndex: (i) => {
76
+ this.activePageIndex = i;
77
+ },
78
+ getCDPSession: () => this.getCDPSession(),
79
+ getCdpEndpoint: () => this.cdpEndpoint,
80
+ });
81
+ }
82
+ isLaunched() {
83
+ if (this.isPersistentContext)
84
+ return true;
85
+ if (!this.browser)
86
+ return false;
87
+ return this.browser.isConnected();
88
+ }
89
+ async getSnapshot(options) {
90
+ const frame = options?.framePath ? this.getFrame(options.framePath) : this.getFrame();
91
+ const snapshot = await getEnhancedSnapshot(frame, options);
92
+ this.refMap = snapshot.refs;
93
+ this.lastSnapshot = snapshot.tree;
94
+ const url = this.pages.length > 0 ? this.getPage().url() : '';
95
+ const elements = [];
96
+ let index = 1;
97
+ for (const [ref, data] of Object.entries(snapshot.refs)) {
98
+ elements.push({
99
+ ref,
100
+ index: index,
101
+ role: data.role,
102
+ name: data.name,
103
+ cssSelector: '',
104
+ xpath: '',
105
+ });
106
+ index++;
107
+ }
108
+ const snapshotId = this.snapshotStore.create(url, elements, options?.framePath);
109
+ const elementCount = elements.length;
110
+ const header = `Snapshot #${snapshotId} (${elementCount} interactive elements)\n---`;
111
+ const tips = `---\nTips:\n Get selector: snapshot --selector-for ${snapshotId}:@e1\n Or by index: snapshot --selector-for ${snapshotId}:1\n List all: snapshot --selectors-of ${snapshotId}\n Validate: snapshot --validate ${snapshotId}`;
112
+ snapshot.tree = `${header}\n${snapshot.tree}\n${tips}`;
113
+ this.lastSnapshot = snapshot.tree;
114
+ return { ...snapshot, snapshotId };
115
+ }
116
+ async ensureSelectorsGenerated(snapId) {
117
+ const store = this.snapshotStore;
118
+ if (store.isSelectorsGenerated(snapId))
119
+ return true;
120
+ const entry = store.get(snapId);
121
+ if (!entry)
122
+ return false;
123
+ const refs = {};
124
+ for (const [ref, el] of entry.elements) {
125
+ refs[ref] = {
126
+ selector: `getByRole('${el.role}'${el.name ? `, { name: "${el.name}", exact: true }` : ''})`,
127
+ role: el.role,
128
+ name: el.name,
129
+ };
130
+ }
131
+ const frame = entry.framePath ? this.getFrame(entry.framePath) : this.getFrame();
132
+ let stableSelectors = {};
133
+ try {
134
+ stableSelectors = await generateStableSelectors(frame, refs);
135
+ }
136
+ catch {
137
+ /* empty */
138
+ }
139
+ for (const [ref, sel] of Object.entries(stableSelectors)) {
140
+ const el = entry.elements.get(ref);
141
+ if (el) {
142
+ el.cssSelector = sel.cssSelector;
143
+ el.xpath = sel.xpath;
144
+ }
145
+ }
146
+ store.markSelectorsGenerated(snapId);
147
+ return true;
148
+ }
149
+ getRefMap() {
150
+ return this.refMap;
151
+ }
152
+ getSnapshotStore() {
153
+ return this.snapshotStore;
154
+ }
155
+ recordCommand(action, selector, value, success) {
156
+ this.commandHistory.push({ action, selector, value, success, timestamp: Date.now() });
157
+ }
158
+ getHistory(filter) {
159
+ let history = this.commandHistory;
160
+ if (filter) {
161
+ history = history.filter((h) => h.selector.includes(filter) || h.action.includes(filter));
162
+ }
163
+ return history;
164
+ }
165
+ clearHistory() {
166
+ this.commandHistory = [];
167
+ }
168
+ getLocatorFromRef(refArg, framePath) {
169
+ const ref = parseRef(refArg);
170
+ if (!ref)
171
+ return null;
172
+ const refData = this.refMap[ref];
173
+ if (!refData)
174
+ return null;
175
+ const frame = this.getFrame(framePath);
176
+ if (refData.role === 'clickable' || refData.role === 'focusable') {
177
+ return frame.locator(refData.selector);
178
+ }
179
+ let locator;
180
+ if (refData.name) {
181
+ locator = frame.getByRole(refData.role, {
182
+ name: refData.name,
183
+ exact: true,
184
+ });
185
+ }
186
+ else {
187
+ locator = frame.getByRole(refData.role);
188
+ }
189
+ if (refData.nth !== undefined) {
190
+ locator = locator.nth(refData.nth);
191
+ }
192
+ return locator;
193
+ }
194
+ isRef(selector) {
195
+ return parseRef(selector) !== null;
196
+ }
197
+ getLocator(selectorOrRef, framePath) {
198
+ const locator = this.getLocatorFromRef(selectorOrRef, framePath);
199
+ if (locator)
200
+ return locator;
201
+ const frame = framePath ? this.getFrame(framePath) : this.getFrame();
202
+ return frame.locator(selectorOrRef);
203
+ }
204
+ getPage() {
205
+ if (this.pages.length === 0) {
206
+ throw new Error('Browser not launched. Call launch first.');
207
+ }
208
+ return this.pages[this.activePageIndex];
209
+ }
210
+ getFrame(framePath) {
211
+ if (!framePath) {
212
+ return this.getPage().mainFrame();
213
+ }
214
+ return this.getFrameByPath(framePath);
215
+ }
216
+ getFrameByPath(framePath) {
217
+ const page = this.getPage();
218
+ const selectors = framePath
219
+ .split('/')
220
+ .map((s) => s.trim())
221
+ .filter(Boolean);
222
+ if (selectors.length === 0) {
223
+ return page.mainFrame();
224
+ }
225
+ let current = page.mainFrame();
226
+ for (let i = 0; i < selectors.length; i++) {
227
+ const selector = selectors[i];
228
+ const childFrames = current.childFrames();
229
+ if (childFrames.length === 0) {
230
+ throw new Error(`No child frames found for selector "${selector}" at path position ${i + 1}. ` +
231
+ `Path: "${framePath}". ` +
232
+ `Current frame has no child frames.`);
233
+ }
234
+ const matchedFrame = this.findMatchingFrame(childFrames, selector);
235
+ if (!matchedFrame) {
236
+ const availableInfo = childFrames.map((f, idx) => ({
237
+ index: idx,
238
+ name: f.name(),
239
+ url: f.url(),
240
+ }));
241
+ const suggestion = childFrames.length > 0
242
+ ? ` Use 'agent-browser frames' to list all iframes, or try: ${childFrames.map((f, idx) => `--in-frame "${idx}"`).join(', ')}`
243
+ : '';
244
+ throw new Error(`Frame not found for selector "${selector}" at path position ${i + 1}. ` +
245
+ `Path: "${framePath}". ` +
246
+ `Available child frames: ${JSON.stringify(availableInfo, null, 2)}.${suggestion}`);
247
+ }
248
+ current = matchedFrame;
249
+ }
250
+ return current;
251
+ }
252
+ findMatchingFrame(frames, selector) {
253
+ const indexMatch = selector.match(/^(\d+)$/);
254
+ if (indexMatch) {
255
+ const index = parseInt(indexMatch[1], 10);
256
+ return frames[index];
257
+ }
258
+ const cleanSelector = selector.replace('#', '');
259
+ const nameMatch = frames.find((f) => f.name() === selector || f.name() === cleanSelector);
260
+ if (nameMatch)
261
+ return nameMatch;
262
+ const urlPathMatch = frames.find((f) => {
263
+ const url = f.url();
264
+ return url.includes(`/${cleanSelector}`) || url.endsWith(`/${cleanSelector}`);
265
+ });
266
+ if (urlPathMatch)
267
+ return urlPathMatch;
268
+ return undefined;
269
+ }
270
+ listFrames() {
271
+ const page = this.getPage();
272
+ const result = [];
273
+ const walk = (frame, pathSoFar) => {
274
+ const children = frame.childFrames();
275
+ for (let i = 0; i < children.length; i++) {
276
+ const child = children[i];
277
+ const name = child.name() || '';
278
+ const segment = name || String(i);
279
+ const childPath = pathSoFar ? `${pathSoFar}/${segment}` : segment;
280
+ result.push({ name, url: child.url(), path: childPath });
281
+ walk(child, childPath);
282
+ }
283
+ };
284
+ walk(page.mainFrame(), '');
285
+ return result;
286
+ }
287
+ setDialogHandler(response, promptText) {
288
+ const page = this.getPage();
289
+ if (this.dialogHandler) {
290
+ page.removeListener('dialog', this.dialogHandler);
291
+ }
292
+ this.dialogHandler = async (dialog) => {
293
+ if (response === 'accept') {
294
+ await dialog.accept(promptText);
295
+ }
296
+ else {
297
+ await dialog.dismiss();
298
+ }
299
+ };
300
+ page.on('dialog', this.dialogHandler);
301
+ }
302
+ clearDialogHandler() {
303
+ if (this.dialogHandler) {
304
+ const page = this.getPage();
305
+ page.removeListener('dialog', this.dialogHandler);
306
+ this.dialogHandler = null;
307
+ }
308
+ }
309
+ startRequestTracking(captureResponse = false) {
310
+ this.network.startRequestTracking(captureResponse);
311
+ }
312
+ get trackingEnabled() {
313
+ return this.network.trackingEnabled;
314
+ }
315
+ getRequests(filter, type) {
316
+ return this.network.getRequests(filter, type);
317
+ }
318
+ clearRequests() {
319
+ this.network.clearRequests();
320
+ }
321
+ startWebSocketTracking() {
322
+ this.network.startWebSocketTracking();
323
+ }
324
+ get wsTrackingEnabled() {
325
+ return this.network.wsTrackingEnabled;
326
+ }
327
+ getWebSockets(filter) {
328
+ return this.network.getWebSockets(filter);
329
+ }
330
+ clearWebSockets() {
331
+ this.network.clearWebSockets();
332
+ }
333
+ saveRequestsToDir(outputDir, filter, type) {
334
+ return this.network.saveRequestsToDir(outputDir, filter, type);
335
+ }
336
+ startConsoleTracking() {
337
+ this.network.startConsoleTracking();
338
+ }
339
+ getConsoleMessages() {
340
+ return this.network.getConsoleMessages();
341
+ }
342
+ clearConsoleMessages() {
343
+ this.network.clearConsoleMessages();
344
+ }
345
+ startErrorTracking() {
346
+ this.network.startErrorTracking();
347
+ }
348
+ getPageErrors() {
349
+ return this.network.getPageErrors();
350
+ }
351
+ clearPageErrors() {
352
+ this.network.clearPageErrors();
353
+ }
354
+ async addRoute(url, options) {
355
+ const page = this.getPage();
356
+ const handler = async (route) => {
357
+ if (options.abort) {
358
+ await route.abort();
359
+ }
360
+ else if (options.response) {
361
+ await route.fulfill({
362
+ status: options.response.status ?? 200,
363
+ body: options.response.body ?? '',
364
+ contentType: options.response.contentType ?? 'text/plain',
365
+ headers: options.response.headers,
366
+ });
367
+ }
368
+ else {
369
+ await route.continue();
370
+ }
371
+ };
372
+ this.routes.set(url, handler);
373
+ await page.route(url, handler);
374
+ }
375
+ async removeRoute(url) {
376
+ const page = this.getPage();
377
+ if (url) {
378
+ const handler = this.routes.get(url);
379
+ if (handler) {
380
+ await page.unroute(url, handler);
381
+ this.routes.delete(url);
382
+ }
383
+ }
384
+ else {
385
+ for (const [routeUrl, handler] of this.routes) {
386
+ await page.unroute(routeUrl, handler);
387
+ }
388
+ this.routes.clear();
389
+ }
390
+ }
391
+ async setGeolocation(latitude, longitude, accuracy) {
392
+ const context = this.contexts[0];
393
+ if (context) {
394
+ await context.setGeolocation({ latitude, longitude, accuracy });
395
+ }
396
+ }
397
+ async setPermissions(permissions, grant) {
398
+ const context = this.contexts[0];
399
+ if (context) {
400
+ if (grant) {
401
+ await context.grantPermissions(permissions);
402
+ }
403
+ else {
404
+ await context.clearPermissions();
405
+ }
406
+ }
407
+ }
408
+ async setViewport(width, height) {
409
+ const page = this.getPage();
410
+ await page.setViewportSize({ width, height });
411
+ }
412
+ async setDeviceScaleFactor(deviceScaleFactor, width, height, mobile = false) {
413
+ const cdp = await this.getCDPSession();
414
+ await cdp.send('Emulation.setDeviceMetricsOverride', {
415
+ width,
416
+ height,
417
+ deviceScaleFactor,
418
+ mobile,
419
+ });
420
+ }
421
+ async clearDeviceMetricsOverride() {
422
+ const cdp = await this.getCDPSession();
423
+ await cdp.send('Emulation.clearDeviceMetricsOverride');
424
+ }
425
+ getDevice(deviceName) {
426
+ return devices[deviceName];
427
+ }
428
+ listDevices() {
429
+ return Object.keys(devices);
430
+ }
431
+ async startHarRecording() {
432
+ this.isRecordingHar = true;
433
+ }
434
+ isHarRecording() {
435
+ return this.isRecordingHar;
436
+ }
437
+ async setOffline(offline) {
438
+ const context = this.contexts[0];
439
+ if (context) {
440
+ await context.setOffline(offline);
441
+ }
442
+ }
443
+ async setExtraHeaders(headers) {
444
+ const context = this.contexts[0];
445
+ if (context) {
446
+ await context.setExtraHTTPHeaders(headers);
447
+ }
448
+ }
449
+ async setScopedHeaders(origin, headers) {
450
+ const page = this.getPage();
451
+ let urlPattern;
452
+ try {
453
+ const url = new URL(origin.startsWith('http') ? origin : `https://${origin}`);
454
+ urlPattern = `**://${url.host}/**`;
455
+ }
456
+ catch {
457
+ urlPattern = `**://${origin}/**`;
458
+ }
459
+ const existingHandler = this.scopedHeaderRoutes.get(urlPattern);
460
+ if (existingHandler) {
461
+ await page.unroute(urlPattern, existingHandler);
462
+ }
463
+ const handler = async (route) => {
464
+ const requestHeaders = route.request().headers();
465
+ await route.continue({
466
+ headers: {
467
+ ...requestHeaders,
468
+ ...headers,
469
+ },
470
+ });
471
+ };
472
+ this.scopedHeaderRoutes.set(urlPattern, handler);
473
+ await page.route(urlPattern, handler);
474
+ }
475
+ async clearScopedHeaders(origin) {
476
+ const page = this.getPage();
477
+ if (origin) {
478
+ let urlPattern;
479
+ try {
480
+ const url = new URL(origin.startsWith('http') ? origin : `https://${origin}`);
481
+ urlPattern = `**://${url.host}/**`;
482
+ }
483
+ catch {
484
+ urlPattern = `**://${origin}/**`;
485
+ }
486
+ const handler = this.scopedHeaderRoutes.get(urlPattern);
487
+ if (handler) {
488
+ await page.unroute(urlPattern, handler);
489
+ this.scopedHeaderRoutes.delete(urlPattern);
490
+ }
491
+ }
492
+ else {
493
+ for (const [pattern, handler] of this.scopedHeaderRoutes) {
494
+ await page.unroute(pattern, handler);
495
+ }
496
+ this.scopedHeaderRoutes.clear();
497
+ }
498
+ }
499
+ async startTracing(options) {
500
+ const context = this.contexts[0];
501
+ if (context) {
502
+ await context.tracing.start({
503
+ screenshots: options.screenshots ?? true,
504
+ snapshots: options.snapshots ?? true,
505
+ });
506
+ }
507
+ }
508
+ async stopTracing(path) {
509
+ const context = this.contexts[0];
510
+ if (context) {
511
+ await context.tracing.stop({ path });
512
+ }
513
+ }
514
+ async saveStorageState(path) {
515
+ const context = this.contexts[0];
516
+ if (context) {
517
+ await context.storageState({ path });
518
+ }
519
+ }
520
+ getPages() {
521
+ return this.pages;
522
+ }
523
+ getActiveIndex() {
524
+ return this.activePageIndex;
525
+ }
526
+ getBrowser() {
527
+ return this.browser;
528
+ }
529
+ isCdpConnectionAlive() {
530
+ if (!this.browser)
531
+ return false;
532
+ try {
533
+ const contexts = this.browser.contexts();
534
+ if (contexts.length === 0) {
535
+ return false;
536
+ }
537
+ return contexts.some((context) => context.pages().length > 0);
538
+ }
539
+ catch {
540
+ return false;
541
+ }
542
+ }
543
+ needsCdpReconnect(cdpEndpoint) {
544
+ if (!this.browser?.isConnected()) {
545
+ return true;
546
+ }
547
+ if (this.cdpEndpoint !== cdpEndpoint) {
548
+ return true;
549
+ }
550
+ if (!this.isCdpConnectionAlive()) {
551
+ return true;
552
+ }
553
+ return false;
554
+ }
555
+ async launch(options) {
556
+ const cdpEndpoint = options.cdpUrl ?? (options.cdpPort ? String(options.cdpPort) : undefined);
557
+ const hasExtensions = !!options.extensions?.length;
558
+ const hasProfile = !!options.profile;
559
+ const hasStorageState = !!options.storageState;
560
+ if (hasExtensions && cdpEndpoint) {
561
+ throw new Error('Extensions cannot be used with CDP connection');
562
+ }
563
+ if (hasProfile && cdpEndpoint) {
564
+ throw new Error('Profile cannot be used with CDP connection');
565
+ }
566
+ if (hasStorageState && hasProfile) {
567
+ throw new Error('Storage state cannot be used with profile (profile is already persistent storage)');
568
+ }
569
+ if (hasStorageState && hasExtensions) {
570
+ throw new Error('Storage state cannot be used with extensions (extensions require persistent context)');
571
+ }
572
+ if (this.browser && !this.browser.isConnected()) {
573
+ await this.close();
574
+ }
575
+ if (this.isLaunched()) {
576
+ const needsRelaunch = (!cdpEndpoint && this.cdpEndpoint !== null) ||
577
+ (!!cdpEndpoint && this.needsCdpReconnect(cdpEndpoint));
578
+ if (needsRelaunch) {
579
+ await this.close();
580
+ }
581
+ else {
582
+ return;
583
+ }
584
+ }
585
+ if (cdpEndpoint) {
586
+ const result = await connectViaCDP(cdpEndpoint, {
587
+ addContext: (ctx) => this.contexts.push(ctx),
588
+ addPage: (p) => this.pages.push(p),
589
+ setupContextTracking: (ctx) => this.setupContextTracking(ctx),
590
+ setupPageTracking: (p) => this.setupPageTracking(p),
591
+ });
592
+ this.browser = result.browser;
593
+ this.cdpEndpoint = result.cdpEndpoint;
594
+ this.activePageIndex = 0;
595
+ return;
596
+ }
597
+ const provider = options.provider ?? process.env.AGENT_BROWSER_PROVIDER;
598
+ if (provider === 'browserbase') {
599
+ const session = await connectToBrowserbase();
600
+ this.browser = session.browser;
601
+ this.browserbaseSessionId = session.sessionId;
602
+ this.browserbaseApiKey = session.apiKey;
603
+ session.context.setDefaultTimeout(10000);
604
+ this.contexts.push(session.context);
605
+ this.setupContextTracking(session.context);
606
+ this.pages.push(session.page);
607
+ this.activePageIndex = 0;
608
+ this.setupPageTracking(session.page);
609
+ return;
610
+ }
611
+ if (provider === 'browseruse') {
612
+ const session = await connectToBrowserUse();
613
+ this.browser = session.browser;
614
+ this.browserUseSessionId = session.sessionId;
615
+ this.browserUseApiKey = session.apiKey;
616
+ session.context.setDefaultTimeout(60000);
617
+ this.contexts.push(session.context);
618
+ this.pages.push(session.page);
619
+ this.activePageIndex = 0;
620
+ this.setupPageTracking(session.page);
621
+ this.setupContextTracking(session.context);
622
+ return;
623
+ }
624
+ if (provider === 'kernel') {
625
+ const session = await connectToKernel();
626
+ this.browser = session.browser;
627
+ this.kernelSessionId = session.sessionId;
628
+ this.kernelApiKey = session.apiKey;
629
+ session.context.setDefaultTimeout(60000);
630
+ this.contexts.push(session.context);
631
+ this.pages.push(session.page);
632
+ this.activePageIndex = 0;
633
+ this.setupPageTracking(session.page);
634
+ this.setupContextTracking(session.context);
635
+ return;
636
+ }
637
+ const browserType = options.browser ?? 'chromium';
638
+ if (hasExtensions && browserType !== 'chromium') {
639
+ throw new Error('Extensions are only supported in Chromium');
640
+ }
641
+ if (options.allowFileAccess && browserType !== 'chromium') {
642
+ throw new Error('allowFileAccess is only supported in Chromium');
643
+ }
644
+ const launcher = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
645
+ const viewport = options.viewport ?? { width: 1280, height: 720 };
646
+ const fileAccessArgs = options.allowFileAccess
647
+ ? ['--allow-file-access-from-files', '--allow-file-access']
648
+ : [];
649
+ const isHeaded = hasExtensions || options.headless === false;
650
+ const antiDetectionArgs = [
651
+ '--disable-blink-features=AutomationControlled',
652
+ '--disable-dev-shm-usage',
653
+ '--no-sandbox',
654
+ ...(isHeaded ? [] : ['--disable-gpu']),
655
+ '--enable-features=WebGL',
656
+ '--ignore-gpu-blacklist',
657
+ ...(isHeaded ? ['--use-gl=desktop', '--enable-gpu-compositing'] : []),
658
+ ];
659
+ const baseArgs = options.args
660
+ ? [...fileAccessArgs, ...antiDetectionArgs, ...options.args]
661
+ : [...fileAccessArgs, ...antiDetectionArgs];
662
+ let context;
663
+ if (hasExtensions) {
664
+ const extPaths = (options.extensions ?? []).join(',');
665
+ const session = process.env.AGENT_BROWSER_SESSION || 'default';
666
+ const extArgs = [`--disable-extensions-except=${extPaths}`, `--load-extension=${extPaths}`];
667
+ const allArgs = baseArgs ? [...extArgs, ...baseArgs] : extArgs;
668
+ context = await launcher.launchPersistentContext(path.join(os.tmpdir(), `agent-browser-ext-${session}`), {
669
+ headless: false,
670
+ executablePath: options.executablePath,
671
+ args: allArgs,
672
+ viewport,
673
+ extraHTTPHeaders: options.headers,
674
+ userAgent: options.userAgent,
675
+ ...(options.proxy && { proxy: options.proxy }),
676
+ ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
677
+ });
678
+ this.isPersistentContext = true;
679
+ }
680
+ else if (hasProfile) {
681
+ const profilePath = (options.profile ?? '').replace(/^~\//, os.homedir() + '/');
682
+ context = await launcher.launchPersistentContext(profilePath, {
683
+ headless: options.headless ?? true,
684
+ executablePath: options.executablePath,
685
+ args: baseArgs,
686
+ viewport,
687
+ extraHTTPHeaders: options.headers,
688
+ userAgent: options.userAgent,
689
+ ...(options.proxy && { proxy: options.proxy }),
690
+ ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
691
+ });
692
+ this.isPersistentContext = true;
693
+ }
694
+ else {
695
+ this.browser = await launcher.launch({
696
+ headless: options.headless ?? true,
697
+ executablePath: options.executablePath,
698
+ args: baseArgs,
699
+ });
700
+ this.cdpEndpoint = null;
701
+ context = await this.browser.newContext({
702
+ viewport,
703
+ extraHTTPHeaders: options.headers,
704
+ userAgent: options.userAgent,
705
+ ...(options.proxy && { proxy: options.proxy }),
706
+ ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
707
+ ...(options.storageState && { storageState: options.storageState }),
708
+ });
709
+ }
710
+ await context.addInitScript(() => {
711
+ if (!window.chrome) {
712
+ window.chrome = {
713
+ runtime: {},
714
+ loadTimes: function () { },
715
+ csi: function () { },
716
+ app: {},
717
+ };
718
+ }
719
+ Object.defineProperty(navigator, 'plugins', {
720
+ get: () => [
721
+ {
722
+ name: 'Chrome PDF Plugin',
723
+ filename: 'internal-pdf-viewer',
724
+ description: 'Portable Document Format',
725
+ },
726
+ {
727
+ name: 'Chrome PDF Viewer',
728
+ filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
729
+ description: '',
730
+ },
731
+ {
732
+ name: 'Native Client',
733
+ filename: 'internal-nacl-plugin',
734
+ description: '',
735
+ },
736
+ ],
737
+ });
738
+ Object.defineProperty(navigator, 'mimeTypes', {
739
+ get: () => [
740
+ {
741
+ type: 'application/pdf',
742
+ suffixes: 'pdf',
743
+ description: 'Portable Document Format',
744
+ },
745
+ {
746
+ type: 'application/x-google-chrome-pdf',
747
+ suffixes: 'pdf',
748
+ description: 'Portable Document Format',
749
+ },
750
+ ],
751
+ });
752
+ });
753
+ context.setDefaultTimeout(60000);
754
+ this.contexts.push(context);
755
+ this.setupContextTracking(context);
756
+ const page = context.pages()[0] ?? (await context.newPage());
757
+ if (!this.pages.includes(page)) {
758
+ this.pages.push(page);
759
+ this.setupPageTracking(page);
760
+ }
761
+ this.activePageIndex = this.pages.length > 0 ? this.pages.length - 1 : 0;
762
+ }
763
+ setupPageTracking(page) {
764
+ this.network.setupPageTracking(page);
765
+ page.on('load', async () => {
766
+ const callbacks = getEventCallbacks();
767
+ callbacks.onNavigation?.({
768
+ url: page.url(),
769
+ title: await page.title().catch(() => ''),
770
+ });
771
+ });
772
+ page.on('close', () => {
773
+ const index = this.pages.indexOf(page);
774
+ if (index !== -1) {
775
+ this.pages.splice(index, 1);
776
+ if (this.activePageIndex >= this.pages.length) {
777
+ this.activePageIndex = Math.max(0, this.pages.length - 1);
778
+ }
779
+ const callbacks = getEventCallbacks();
780
+ callbacks.onTabClosed?.({
781
+ index,
782
+ remainingTabs: this.pages.length,
783
+ });
784
+ }
785
+ });
786
+ }
787
+ async setupContextTracking(context) {
788
+ context.on('page', async (page) => {
789
+ if (!this.pages.includes(page)) {
790
+ this.pages.push(page);
791
+ this.setupPageTracking(page);
792
+ }
793
+ const callbacks = getEventCallbacks();
794
+ if (callbacks.onTabCreated) {
795
+ const index = this.pages.length - 1;
796
+ callbacks.onTabCreated({
797
+ index,
798
+ url: page.url(),
799
+ title: await page.title().catch(() => ''),
800
+ });
801
+ }
802
+ const newIndex = this.pages.indexOf(page);
803
+ if (newIndex !== -1 && newIndex !== this.activePageIndex) {
804
+ this.activePageIndex = newIndex;
805
+ this.invalidateCDPSession().catch(() => { });
806
+ }
807
+ });
808
+ }
809
+ async newTab() {
810
+ if (!this.browser || this.contexts.length === 0) {
811
+ throw new Error('Browser not launched');
812
+ }
813
+ await this.invalidateCDPSession();
814
+ const context = this.contexts[0];
815
+ const page = await context.newPage();
816
+ if (!this.pages.includes(page)) {
817
+ this.pages.push(page);
818
+ this.setupPageTracking(page);
819
+ }
820
+ this.activePageIndex = this.pages.length - 1;
821
+ const callbacks = getEventCallbacks();
822
+ if (callbacks.onTabCreated) {
823
+ const index = this.pages.length - 1;
824
+ callbacks.onTabCreated({
825
+ index,
826
+ url: page.url(),
827
+ title: await page.title().catch(() => ''),
828
+ });
829
+ }
830
+ return { index: this.activePageIndex, total: this.pages.length };
831
+ }
832
+ async newWindow(viewport) {
833
+ if (!this.browser) {
834
+ throw new Error('Browser not launched');
835
+ }
836
+ const context = await this.browser.newContext({
837
+ viewport: viewport ?? { width: 1280, height: 720 },
838
+ });
839
+ context.setDefaultTimeout(60000);
840
+ this.contexts.push(context);
841
+ this.setupContextTracking(context);
842
+ const page = await context.newPage();
843
+ if (!this.pages.includes(page)) {
844
+ this.pages.push(page);
845
+ this.setupPageTracking(page);
846
+ }
847
+ this.activePageIndex = this.pages.length - 1;
848
+ const callbacks = getEventCallbacks();
849
+ if (callbacks.onTabCreated) {
850
+ const index = this.pages.length - 1;
851
+ callbacks.onTabCreated({
852
+ index,
853
+ url: page.url(),
854
+ title: await page.title().catch(() => ''),
855
+ });
856
+ }
857
+ return { index: this.activePageIndex, total: this.pages.length };
858
+ }
859
+ async invalidateCDPSession() {
860
+ const shouldRestart = this.screencast.shouldBeActive;
861
+ const savedCallback = this.screencast.savedCallback;
862
+ const savedOptions = this.screencast.savedOptions;
863
+ if (this.screencast.active) {
864
+ await this.screencast.stopScreencastInternal();
865
+ }
866
+ if (this.cdpSession) {
867
+ await this.cdpSession.detach().catch(() => { });
868
+ this.cdpSession = null;
869
+ }
870
+ if (shouldRestart && savedCallback) {
871
+ try {
872
+ await this.screencast.startScreencast(savedCallback, savedOptions ?? undefined);
873
+ }
874
+ catch {
875
+ // Ignore errors when restarting screencast on new page
876
+ }
877
+ }
878
+ }
879
+ async switchTo(index) {
880
+ if (index < 0 || index >= this.pages.length) {
881
+ throw new Error(`Invalid tab index: ${index}. Available: 0-${this.pages.length - 1}`);
882
+ }
883
+ if (index !== this.activePageIndex) {
884
+ await this.invalidateCDPSession();
885
+ }
886
+ const previousIndex = this.activePageIndex;
887
+ this.activePageIndex = index;
888
+ const page = this.pages[index];
889
+ if (this.recorder.getSessionId() && previousIndex !== index) {
890
+ this.recorder.addStep({
891
+ id: `step-${Date.now()}`,
892
+ timestamp: Date.now(),
893
+ action: 'tab_switch',
894
+ index: index,
895
+ });
896
+ }
897
+ const callbacks = getEventCallbacks();
898
+ callbacks.onTabSwitched?.({
899
+ fromIndex: previousIndex,
900
+ toIndex: index,
901
+ });
902
+ return {
903
+ index: this.activePageIndex,
904
+ url: page.url(),
905
+ title: '',
906
+ };
907
+ }
908
+ async closeTab(index) {
909
+ const targetIndex = index ?? this.activePageIndex;
910
+ if (targetIndex < 0 || targetIndex >= this.pages.length) {
911
+ throw new Error(`Invalid tab index: ${targetIndex}`);
912
+ }
913
+ if (this.pages.length === 1) {
914
+ throw new Error('Cannot close the last tab. Use "close" to close the browser.');
915
+ }
916
+ if (this.recorder.getSessionId()) {
917
+ this.recorder.addStep({
918
+ id: `step-${Date.now()}`,
919
+ timestamp: Date.now(),
920
+ action: 'tab_close',
921
+ index: targetIndex,
922
+ });
923
+ }
924
+ if (targetIndex === this.activePageIndex) {
925
+ await this.invalidateCDPSession();
926
+ }
927
+ const page = this.pages[targetIndex];
928
+ await page.close();
929
+ this.pages.splice(targetIndex, 1);
930
+ if (this.activePageIndex >= this.pages.length) {
931
+ this.activePageIndex = this.pages.length - 1;
932
+ }
933
+ else if (this.activePageIndex > targetIndex) {
934
+ this.activePageIndex--;
935
+ }
936
+ return { closed: targetIndex, remaining: this.pages.length };
937
+ }
938
+ async listTabs() {
939
+ const tabs = await Promise.all(this.pages.map(async (page, index) => ({
940
+ index,
941
+ url: page.url(),
942
+ title: await page.title().catch(() => ''),
943
+ active: index === this.activePageIndex,
944
+ })));
945
+ return tabs;
946
+ }
947
+ async getCDPSession() {
948
+ if (this.cdpSession) {
949
+ return this.cdpSession;
950
+ }
951
+ const page = this.getPage();
952
+ const context = page.context();
953
+ this.cdpSession = await context.newCDPSession(page);
954
+ return this.cdpSession;
955
+ }
956
+ isScreencasting() {
957
+ return this.screencast.isScreencasting();
958
+ }
959
+ async startScreencast(callback, options) {
960
+ return this.screencast.startScreencast(callback, options);
961
+ }
962
+ async stopScreencast() {
963
+ return this.screencast.stopScreencast();
964
+ }
965
+ async injectMouseEvent(params) {
966
+ return this.screencast.injectMouseEvent(params);
967
+ }
968
+ async injectKeyboardEvent(params) {
969
+ return this.screencast.injectKeyboardEvent(params);
970
+ }
971
+ async injectTouchEvent(params) {
972
+ return this.screencast.injectTouchEvent(params);
973
+ }
974
+ async insertText(text) {
975
+ return this.screencast.insertText(text);
976
+ }
977
+ _lastFillSelector = '';
978
+ _lastFillValue = '';
979
+ _fillFocusedSelector = '';
980
+ async fillValue(selector, value) {
981
+ const page = this.getPage();
982
+ if (!page)
983
+ return;
984
+ if (!selector || value === undefined)
985
+ return;
986
+ if (value === this._lastFillValue && selector === this._lastFillSelector)
987
+ return;
988
+ this._lastFillSelector = selector;
989
+ this._lastFillValue = value;
990
+ const needsFocus = !this._fillFocusedSelector || this._fillFocusedSelector !== selector;
991
+ await page.evaluate(({ selector, value, needsFocus }) => {
992
+ const el = document.querySelector(selector);
993
+ if (!el)
994
+ return { ok: false, reason: 'not_found' };
995
+ const isContentEditable = el instanceof HTMLElement &&
996
+ (el.isContentEditable || el.getAttribute('contenteditable') === 'true');
997
+ if (needsFocus && !isContentEditable) {
998
+ el.focus();
999
+ }
1000
+ if (isContentEditable) {
1001
+ if (needsFocus)
1002
+ el.focus();
1003
+ document.execCommand('selectAll', false, undefined);
1004
+ document.execCommand('insertText', false, value);
1005
+ return { ok: true, method: 'contenteditable' };
1006
+ }
1007
+ const tag = el.tagName.toLowerCase();
1008
+ const isInput = tag === 'input';
1009
+ const isTextarea = tag === 'textarea';
1010
+ if (!isInput && !isTextarea) {
1011
+ return { ok: false, reason: 'not_input' };
1012
+ }
1013
+ const proto = isInput
1014
+ ? window.HTMLInputElement.prototype
1015
+ : window.HTMLTextAreaElement.prototype;
1016
+ const nativeInputValueSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
1017
+ if (nativeInputValueSetter) {
1018
+ nativeInputValueSetter.call(el, value);
1019
+ }
1020
+ else {
1021
+ el.value = value;
1022
+ }
1023
+ el.dispatchEvent(new InputEvent('input', {
1024
+ bubbles: true,
1025
+ cancelable: true,
1026
+ inputType: 'insertReplacementText',
1027
+ data: value,
1028
+ }));
1029
+ return { ok: true, method: 'native_setter' };
1030
+ }, { selector, value, needsFocus });
1031
+ if (needsFocus) {
1032
+ this._fillFocusedSelector = selector;
1033
+ }
1034
+ }
1035
+ clearFillState(selector) {
1036
+ if (selector && this._fillFocusedSelector === selector) {
1037
+ this._fillFocusedSelector = '';
1038
+ }
1039
+ if (!selector) {
1040
+ this._fillFocusedSelector = '';
1041
+ this._lastFillSelector = '';
1042
+ this._lastFillValue = '';
1043
+ }
1044
+ }
1045
+ async blurElement(selector) {
1046
+ const page = this.getPage();
1047
+ if (!page)
1048
+ return;
1049
+ await page.evaluate((sel) => {
1050
+ const el = document.querySelector(sel);
1051
+ if (el)
1052
+ el.blur();
1053
+ }, selector);
1054
+ }
1055
+ async pressKey(key) {
1056
+ const page = this.getPage();
1057
+ if (!page)
1058
+ return;
1059
+ await page.keyboard.press(key);
1060
+ }
1061
+ async injectFocusListener(onEvent) {
1062
+ const page = this.getPage();
1063
+ if (!page)
1064
+ return;
1065
+ try {
1066
+ await page.exposeFunction('__agentBrowserInputEvent', (data) => {
1067
+ onEvent(data);
1068
+ });
1069
+ }
1070
+ catch {
1071
+ // Already registered from previous injection - safe to continue
1072
+ }
1073
+ const injectScript = `
1074
+ (function() {
1075
+ if (window.__agentBrowserListenerInjected) return;
1076
+ window.__agentBrowserListenerInjected = true;
1077
+
1078
+ document.addEventListener('focus', function(e) {
1079
+ var el = e.target;
1080
+ if (!el) return;
1081
+ var tag = el.tagName;
1082
+ if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !el.isContentEditable) return;
1083
+ try {
1084
+ window.__agentBrowserInputEvent({
1085
+ type: 'input_focused',
1086
+ tag: tag,
1087
+ inputType: el.type || '',
1088
+ value: typeof el.value === 'string' ? el.value : '',
1089
+ placeholder: el.placeholder || '',
1090
+ id: el.id || '',
1091
+ selector: (function() {
1092
+ if (el.id) return '#' + el.id;
1093
+ if (el.name && el.name) return '[name="' + el.name + '"]';
1094
+ return el.tagName.toLowerCase();
1095
+ })()
1096
+ });
1097
+ } catch(ex) {}
1098
+ }, true);
1099
+
1100
+ document.addEventListener('input', function(e) {
1101
+ var el = e.target;
1102
+ if (!el) return;
1103
+ var tag = el.tagName;
1104
+ if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !el.isContentEditable) return;
1105
+ try {
1106
+ window.__agentBrowserInputEvent({
1107
+ type: 'input_value',
1108
+ text: typeof el.value === 'string' ? el.value : ''
1109
+ });
1110
+ } catch(ex) {}
1111
+ }, true);
1112
+
1113
+ document.addEventListener('blur', function() {
1114
+ try {
1115
+ window.__agentBrowserInputEvent({ type: 'input_blur' });
1116
+ } catch(ex) {}
1117
+ }, true);
1118
+ })();
1119
+ `;
1120
+ await page.addInitScript(injectScript);
1121
+ await page.evaluate(injectScript);
1122
+ }
1123
+ isRecording() {
1124
+ return this.recording.isRecording();
1125
+ }
1126
+ isRecordingSession() {
1127
+ return this.recorder.isRecordingSession();
1128
+ }
1129
+ async injectRecorderIfNeeded() {
1130
+ return this.recorder.injectRecorderIfNeeded();
1131
+ }
1132
+ get recorderPaused() {
1133
+ return this.recorder.recorderPaused;
1134
+ }
1135
+ set recorderPaused(val) {
1136
+ this.recorder.recorderPaused = val;
1137
+ }
1138
+ pauseRecording() {
1139
+ this.recorder.pauseRecording();
1140
+ }
1141
+ resumeRecording() {
1142
+ this.recorder.resumeRecording();
1143
+ }
1144
+ recordStep(step) {
1145
+ this.recorder.recordStep(step);
1146
+ }
1147
+ async startRecording(outputPath, url) {
1148
+ return this.recording.startRecording(outputPath, url);
1149
+ }
1150
+ async stopRecording() {
1151
+ return this.recording.stopRecording();
1152
+ }
1153
+ async restartRecording(outputPath, url) {
1154
+ return this.recording.restartRecording(outputPath, url);
1155
+ }
1156
+ async startRecorder(url, hide = false) {
1157
+ return this.recorder.startRecorder(url, hide);
1158
+ }
1159
+ async stopRecorder() {
1160
+ return this.recorder.stopRecorder();
1161
+ }
1162
+ getRecorderStatus() {
1163
+ return this.recorder.getRecorderStatus();
1164
+ }
1165
+ async close() {
1166
+ if (this.recording.isRecording()) {
1167
+ await this.recording.stopRecording();
1168
+ }
1169
+ if (this.screencast.active) {
1170
+ await this.screencast.stopScreencast();
1171
+ }
1172
+ const page = this.pages.length > 0 ? this.getPage() : null;
1173
+ this.recorder.cleanup(page);
1174
+ this.network.cleanup(page);
1175
+ this.routes.clear();
1176
+ if (this.cdpSession) {
1177
+ await this.cdpSession.detach().catch(() => { });
1178
+ this.cdpSession = null;
1179
+ }
1180
+ const closePages = async () => {
1181
+ for (const page of this.pages) {
1182
+ await page.close().catch(() => { });
1183
+ }
1184
+ };
1185
+ const closeBrowser = async () => {
1186
+ if (this.browser) {
1187
+ await this.browser.close().catch(() => { });
1188
+ this.browser = null;
1189
+ }
1190
+ };
1191
+ if (this.browserbaseSessionId && this.browserbaseApiKey) {
1192
+ await closeBrowserbaseSession(this.browserbaseSessionId, this.browserbaseApiKey).catch((error) => {
1193
+ console.error('Failed to close Browserbase session:', error);
1194
+ });
1195
+ this.browser = null;
1196
+ }
1197
+ else if (this.browserUseSessionId && this.browserUseApiKey) {
1198
+ await closeBrowserUseSession(this.browserUseSessionId, this.browserUseApiKey).catch((error) => {
1199
+ console.error('Failed to close Browser Use session:', error);
1200
+ });
1201
+ this.browser = null;
1202
+ }
1203
+ else if (this.kernelSessionId && this.kernelApiKey) {
1204
+ await closeKernelSession(this.kernelSessionId, this.kernelApiKey).catch((error) => {
1205
+ console.error('Failed to close Kernel session:', error);
1206
+ });
1207
+ this.browser = null;
1208
+ }
1209
+ else if (this.cdpEndpoint !== null) {
1210
+ console.log('[DEBUG close] CDP endpoint detected:', this.cdpEndpoint);
1211
+ console.log('[DEBUG close] browser exists:', !!this.browser);
1212
+ if (this.browser) {
1213
+ try {
1214
+ console.log('[DEBUG close] CDP connection - closing pages and disconnecting');
1215
+ await closePages();
1216
+ await this.browser.close();
1217
+ console.log('[DEBUG close] CDP connection closed');
1218
+ }
1219
+ catch (e) {
1220
+ console.log('[DEBUG close] CDP disconnect failed:', e);
1221
+ }
1222
+ finally {
1223
+ this.browser = null;
1224
+ }
1225
+ }
1226
+ }
1227
+ else {
1228
+ await closePages();
1229
+ for (const context of this.contexts) {
1230
+ await context.close().catch(() => { });
1231
+ }
1232
+ await closeBrowser();
1233
+ }
1234
+ this.pages = [];
1235
+ this.contexts = [];
1236
+ this.cdpEndpoint = null;
1237
+ this.browserbaseSessionId = null;
1238
+ this.browserbaseApiKey = null;
1239
+ this.browserUseSessionId = null;
1240
+ this.browserUseApiKey = null;
1241
+ this.kernelSessionId = null;
1242
+ this.kernelApiKey = null;
1243
+ this.isPersistentContext = false;
1244
+ this.activePageIndex = 0;
1245
+ this.refMap = {};
1246
+ this.lastSnapshot = '';
1247
+ this.screencast.cleanup();
1248
+ this.recording.cleanup();
1249
+ }
1250
+ }
1251
+ //# sourceMappingURL=browser-manager.js.map