@hasna/browser 0.3.7 → 0.3.8
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/cli/index.js +400 -335
- package/dist/index.js +8253 -7844
- package/dist/lib/actions.d.ts +1 -0
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/coordination.d.ts.map +1 -1
- package/dist/lib/network.d.ts.map +1 -1
- package/dist/lib/page-memory.d.ts.map +1 -1
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/task-queue.d.ts.map +1 -1
- package/dist/mcp/index.js +594 -529
- package/dist/server/index.js +1048 -312
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -10773,7 +10773,8 @@ function startHAR(page) {
|
|
|
10773
10773
|
},
|
|
10774
10774
|
timings: { send: 0, wait: duration, receive: 0 }
|
|
10775
10775
|
};
|
|
10776
|
-
entries.
|
|
10776
|
+
if (entries.length < HAR_MAX_ENTRIES)
|
|
10777
|
+
entries.push(entry);
|
|
10777
10778
|
};
|
|
10778
10779
|
const onRequestFailed = (req) => {
|
|
10779
10780
|
requestStart.delete(req.url() + req.method());
|
|
@@ -10798,6 +10799,7 @@ function startHAR(page) {
|
|
|
10798
10799
|
}
|
|
10799
10800
|
};
|
|
10800
10801
|
}
|
|
10802
|
+
var HAR_MAX_ENTRIES = 5000;
|
|
10801
10803
|
var init_network = __esm(() => {
|
|
10802
10804
|
init_network_log();
|
|
10803
10805
|
});
|
|
@@ -11200,337 +11202,6 @@ var init_storage_state = __esm(() => {
|
|
|
11200
11202
|
STATES_DIR = join8(getDataDir2(), "states");
|
|
11201
11203
|
});
|
|
11202
11204
|
|
|
11203
|
-
// src/lib/session.ts
|
|
11204
|
-
var exports_session = {};
|
|
11205
|
-
__export(exports_session, {
|
|
11206
|
-
setSessionPage: () => setSessionPage,
|
|
11207
|
-
renameSession: () => renameSession2,
|
|
11208
|
-
listSessions: () => listSessions2,
|
|
11209
|
-
isBunSession: () => isBunSession,
|
|
11210
|
-
isAutoGallery: () => isAutoGallery,
|
|
11211
|
-
hasActiveHandle: () => hasActiveHandle,
|
|
11212
|
-
getTokenBudget: () => getTokenBudget,
|
|
11213
|
-
getSessionPage: () => getSessionPage,
|
|
11214
|
-
getSessionEngine: () => getSessionEngine,
|
|
11215
|
-
getSessionByName: () => getSessionByName2,
|
|
11216
|
-
getSessionBunView: () => getSessionBunView,
|
|
11217
|
-
getSessionBrowser: () => getSessionBrowser,
|
|
11218
|
-
getSession: () => getSession2,
|
|
11219
|
-
getDefaultSession: () => getDefaultSession,
|
|
11220
|
-
getActiveSessions: () => getActiveSessions,
|
|
11221
|
-
getActiveSessionForAgent: () => getActiveSessionForAgent2,
|
|
11222
|
-
createSession: () => createSession2,
|
|
11223
|
-
countActiveSessions: () => countActiveSessions2,
|
|
11224
|
-
closeSession: () => closeSession2,
|
|
11225
|
-
closeAllSessions: () => closeAllSessions,
|
|
11226
|
-
browserPool: () => pool
|
|
11227
|
-
});
|
|
11228
|
-
function createBunProxy(view) {
|
|
11229
|
-
return view;
|
|
11230
|
-
}
|
|
11231
|
-
async function createSession2(opts = {}) {
|
|
11232
|
-
if (opts.cdpUrl) {
|
|
11233
|
-
const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
|
|
11234
|
-
const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
|
|
11235
|
-
const contexts = cdpBrowser.contexts();
|
|
11236
|
-
const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
|
|
11237
|
-
const pages = context.pages();
|
|
11238
|
-
const page2 = pages.length > 0 ? pages[0] : await context.newPage();
|
|
11239
|
-
const session2 = createSession({
|
|
11240
|
-
engine: "cdp",
|
|
11241
|
-
projectId: opts.projectId,
|
|
11242
|
-
agentId: opts.agentId,
|
|
11243
|
-
startUrl: page2.url(),
|
|
11244
|
-
name: opts.name ?? "attached"
|
|
11245
|
-
});
|
|
11246
|
-
const cleanups2 = [];
|
|
11247
|
-
if (opts.captureNetwork !== false) {
|
|
11248
|
-
try {
|
|
11249
|
-
cleanups2.push(enableNetworkLogging(page2, session2.id));
|
|
11250
|
-
} catch {}
|
|
11251
|
-
}
|
|
11252
|
-
if (opts.captureConsole !== false) {
|
|
11253
|
-
try {
|
|
11254
|
-
cleanups2.push(enableConsoleCapture(page2, session2.id));
|
|
11255
|
-
} catch {}
|
|
11256
|
-
}
|
|
11257
|
-
try {
|
|
11258
|
-
cleanups2.push(setupDialogHandler(page2, session2.id));
|
|
11259
|
-
} catch {}
|
|
11260
|
-
handles.set(session2.id, { browser: cdpBrowser, bunView: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
11261
|
-
return { session: session2, page: page2 };
|
|
11262
|
-
}
|
|
11263
|
-
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
11264
|
-
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
11265
|
-
let browser = null;
|
|
11266
|
-
let bunView = null;
|
|
11267
|
-
let page;
|
|
11268
|
-
if (resolvedEngine === "bun") {
|
|
11269
|
-
if (!isBunWebViewAvailable()) {
|
|
11270
|
-
console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
|
|
11271
|
-
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
11272
|
-
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
11273
|
-
} else {
|
|
11274
|
-
bunView = new BunWebViewSession({
|
|
11275
|
-
width: opts.viewport?.width ?? 1280,
|
|
11276
|
-
height: opts.viewport?.height ?? 720,
|
|
11277
|
-
profile: opts.name ?? undefined
|
|
11278
|
-
});
|
|
11279
|
-
if (opts.stealth) {}
|
|
11280
|
-
page = createBunProxy(bunView);
|
|
11281
|
-
}
|
|
11282
|
-
} else if (resolvedEngine === "lightpanda") {
|
|
11283
|
-
browser = await connectLightpanda();
|
|
11284
|
-
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
11285
|
-
page = await context.newPage();
|
|
11286
|
-
} else {
|
|
11287
|
-
browser = await pool.acquire(opts.headless ?? true);
|
|
11288
|
-
if (opts.storageState) {
|
|
11289
|
-
const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
11290
|
-
const statePath2 = loadStatePath2(opts.storageState);
|
|
11291
|
-
if (statePath2) {
|
|
11292
|
-
const context = await browser.newContext({
|
|
11293
|
-
viewport: opts.viewport ?? { width: 1280, height: 720 },
|
|
11294
|
-
userAgent: opts.userAgent,
|
|
11295
|
-
storageState: statePath2
|
|
11296
|
-
});
|
|
11297
|
-
page = await context.newPage();
|
|
11298
|
-
} else {
|
|
11299
|
-
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
11300
|
-
}
|
|
11301
|
-
} else {
|
|
11302
|
-
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
11303
|
-
}
|
|
11304
|
-
}
|
|
11305
|
-
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
11306
|
-
try {
|
|
11307
|
-
return new URL(opts.startUrl).hostname;
|
|
11308
|
-
} catch {
|
|
11309
|
-
return;
|
|
11310
|
-
}
|
|
11311
|
-
})() : undefined);
|
|
11312
|
-
const session = createSession({
|
|
11313
|
-
engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
|
|
11314
|
-
projectId: opts.projectId,
|
|
11315
|
-
agentId: opts.agentId,
|
|
11316
|
-
startUrl: opts.startUrl,
|
|
11317
|
-
name: sessionName
|
|
11318
|
-
});
|
|
11319
|
-
if (opts.stealth && !bunView) {
|
|
11320
|
-
try {
|
|
11321
|
-
await applyStealthPatches(page);
|
|
11322
|
-
} catch {}
|
|
11323
|
-
}
|
|
11324
|
-
const cleanups = [];
|
|
11325
|
-
if (!bunView) {
|
|
11326
|
-
if (opts.captureNetwork !== false) {
|
|
11327
|
-
try {
|
|
11328
|
-
cleanups.push(enableNetworkLogging(page, session.id));
|
|
11329
|
-
} catch {}
|
|
11330
|
-
}
|
|
11331
|
-
if (opts.captureConsole !== false) {
|
|
11332
|
-
try {
|
|
11333
|
-
cleanups.push(enableConsoleCapture(page, session.id));
|
|
11334
|
-
} catch {}
|
|
11335
|
-
}
|
|
11336
|
-
try {
|
|
11337
|
-
cleanups.push(setupDialogHandler(page, session.id));
|
|
11338
|
-
} catch {}
|
|
11339
|
-
} else {
|
|
11340
|
-
if (opts.captureConsole !== false) {
|
|
11341
|
-
try {
|
|
11342
|
-
const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
11343
|
-
await bunView.addInitScript(`
|
|
11344
|
-
(() => {
|
|
11345
|
-
const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
|
|
11346
|
-
['log','warn','error','debug','info'].forEach(level => {
|
|
11347
|
-
console[level] = (...args) => {
|
|
11348
|
-
orig[level](...args);
|
|
11349
|
-
};
|
|
11350
|
-
});
|
|
11351
|
-
})()
|
|
11352
|
-
`);
|
|
11353
|
-
} catch {}
|
|
11354
|
-
}
|
|
11355
|
-
}
|
|
11356
|
-
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
11357
|
-
if (opts.startUrl) {
|
|
11358
|
-
try {
|
|
11359
|
-
if (bunView) {
|
|
11360
|
-
await bunView.goto(opts.startUrl);
|
|
11361
|
-
} else {
|
|
11362
|
-
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
11363
|
-
}
|
|
11364
|
-
} catch {}
|
|
11365
|
-
}
|
|
11366
|
-
return { session, page };
|
|
11367
|
-
}
|
|
11368
|
-
function getSessionPage(sessionId) {
|
|
11369
|
-
const handle = handles.get(sessionId);
|
|
11370
|
-
if (!handle)
|
|
11371
|
-
throw new SessionNotFoundError(sessionId);
|
|
11372
|
-
try {
|
|
11373
|
-
if (handle.bunView) {
|
|
11374
|
-
handle.bunView.url();
|
|
11375
|
-
} else {
|
|
11376
|
-
handle.page.url();
|
|
11377
|
-
}
|
|
11378
|
-
} catch {
|
|
11379
|
-
handles.delete(sessionId);
|
|
11380
|
-
throw new SessionNotFoundError(sessionId);
|
|
11381
|
-
}
|
|
11382
|
-
handle.lastActivity = Date.now();
|
|
11383
|
-
return handle.page;
|
|
11384
|
-
}
|
|
11385
|
-
function getSessionBunView(sessionId) {
|
|
11386
|
-
return handles.get(sessionId)?.bunView ?? null;
|
|
11387
|
-
}
|
|
11388
|
-
function isBunSession(sessionId) {
|
|
11389
|
-
return handles.get(sessionId)?.engine === "bun";
|
|
11390
|
-
}
|
|
11391
|
-
function getSessionBrowser(sessionId) {
|
|
11392
|
-
const handle = handles.get(sessionId);
|
|
11393
|
-
if (!handle)
|
|
11394
|
-
throw new SessionNotFoundError(sessionId);
|
|
11395
|
-
if (!handle.browser)
|
|
11396
|
-
throw new BrowserError("This session uses Bun.WebView (no Playwright browser)", "NO_PLAYWRIGHT_BROWSER");
|
|
11397
|
-
return handle.browser;
|
|
11398
|
-
}
|
|
11399
|
-
function getSessionEngine(sessionId) {
|
|
11400
|
-
const handle = handles.get(sessionId);
|
|
11401
|
-
if (!handle)
|
|
11402
|
-
throw new SessionNotFoundError(sessionId);
|
|
11403
|
-
return handle.engine;
|
|
11404
|
-
}
|
|
11405
|
-
function hasActiveHandle(sessionId) {
|
|
11406
|
-
return handles.has(sessionId);
|
|
11407
|
-
}
|
|
11408
|
-
function setSessionPage(sessionId, page) {
|
|
11409
|
-
const handle = handles.get(sessionId);
|
|
11410
|
-
if (!handle)
|
|
11411
|
-
throw new SessionNotFoundError(sessionId);
|
|
11412
|
-
handle.page = page;
|
|
11413
|
-
}
|
|
11414
|
-
async function closeSession2(sessionId) {
|
|
11415
|
-
const handle = handles.get(sessionId);
|
|
11416
|
-
if (handle) {
|
|
11417
|
-
for (const cleanup of handle.cleanups) {
|
|
11418
|
-
try {
|
|
11419
|
-
cleanup();
|
|
11420
|
-
} catch {}
|
|
11421
|
-
}
|
|
11422
|
-
if (handle.bunView) {
|
|
11423
|
-
try {
|
|
11424
|
-
await handle.bunView.close();
|
|
11425
|
-
} catch {}
|
|
11426
|
-
} else {
|
|
11427
|
-
try {
|
|
11428
|
-
await handle.page.context().close();
|
|
11429
|
-
} catch {}
|
|
11430
|
-
if (handle.browser)
|
|
11431
|
-
pool.release(handle.browser);
|
|
11432
|
-
}
|
|
11433
|
-
handles.delete(sessionId);
|
|
11434
|
-
}
|
|
11435
|
-
return closeSession(sessionId);
|
|
11436
|
-
}
|
|
11437
|
-
function getSession2(sessionId) {
|
|
11438
|
-
return getSession(sessionId);
|
|
11439
|
-
}
|
|
11440
|
-
function listSessions2(filter) {
|
|
11441
|
-
return listSessions(filter);
|
|
11442
|
-
}
|
|
11443
|
-
function getActiveSessions() {
|
|
11444
|
-
return listSessions({ status: "active" });
|
|
11445
|
-
}
|
|
11446
|
-
async function closeAllSessions() {
|
|
11447
|
-
for (const [id] of handles) {
|
|
11448
|
-
await closeSession2(id).catch(() => {});
|
|
11449
|
-
}
|
|
11450
|
-
await pool.destroyAll();
|
|
11451
|
-
}
|
|
11452
|
-
function getSessionByName2(name) {
|
|
11453
|
-
return getSessionByName(name);
|
|
11454
|
-
}
|
|
11455
|
-
function renameSession2(id, name) {
|
|
11456
|
-
return renameSession(id, name);
|
|
11457
|
-
}
|
|
11458
|
-
function getTokenBudget(sessionId) {
|
|
11459
|
-
const handle = handles.get(sessionId);
|
|
11460
|
-
return handle ? handle.tokenBudget : null;
|
|
11461
|
-
}
|
|
11462
|
-
function getActiveSessionForAgent2(agentId) {
|
|
11463
|
-
const session = getActiveSessionForAgent(agentId);
|
|
11464
|
-
if (!session)
|
|
11465
|
-
return null;
|
|
11466
|
-
const handle = handles.get(session.id);
|
|
11467
|
-
if (!handle)
|
|
11468
|
-
return null;
|
|
11469
|
-
try {
|
|
11470
|
-
if (handle.bunView)
|
|
11471
|
-
handle.bunView.url();
|
|
11472
|
-
else
|
|
11473
|
-
handle.page.url();
|
|
11474
|
-
} catch {
|
|
11475
|
-
handles.delete(session.id);
|
|
11476
|
-
return null;
|
|
11477
|
-
}
|
|
11478
|
-
return { session, page: handle.page };
|
|
11479
|
-
}
|
|
11480
|
-
function getDefaultSession() {
|
|
11481
|
-
const session = getDefaultActiveSession();
|
|
11482
|
-
if (!session)
|
|
11483
|
-
return null;
|
|
11484
|
-
const handle = handles.get(session.id);
|
|
11485
|
-
if (!handle)
|
|
11486
|
-
return null;
|
|
11487
|
-
try {
|
|
11488
|
-
if (handle.bunView)
|
|
11489
|
-
handle.bunView.url();
|
|
11490
|
-
else
|
|
11491
|
-
handle.page.url();
|
|
11492
|
-
} catch {
|
|
11493
|
-
handles.delete(session.id);
|
|
11494
|
-
return null;
|
|
11495
|
-
}
|
|
11496
|
-
return { session, page: handle.page };
|
|
11497
|
-
}
|
|
11498
|
-
function isAutoGallery(sessionId) {
|
|
11499
|
-
return handles.get(sessionId)?.autoGallery ?? false;
|
|
11500
|
-
}
|
|
11501
|
-
function countActiveSessions2() {
|
|
11502
|
-
return countActiveSessions();
|
|
11503
|
-
}
|
|
11504
|
-
var handles, pool, SESSION_TTL_MS, ttlInterval;
|
|
11505
|
-
var init_session = __esm(() => {
|
|
11506
|
-
init_types();
|
|
11507
|
-
init_types();
|
|
11508
|
-
init_sessions();
|
|
11509
|
-
init_playwright();
|
|
11510
|
-
init_lightpanda();
|
|
11511
|
-
init_bun_webview();
|
|
11512
|
-
init_selector();
|
|
11513
|
-
init_network();
|
|
11514
|
-
init_console();
|
|
11515
|
-
init_stealth();
|
|
11516
|
-
init_dialogs();
|
|
11517
|
-
handles = new Map;
|
|
11518
|
-
pool = new BrowserPool(5);
|
|
11519
|
-
SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
|
|
11520
|
-
ttlInterval = setInterval(async () => {
|
|
11521
|
-
const now = Date.now();
|
|
11522
|
-
for (const [id, handle] of handles) {
|
|
11523
|
-
if (now - handle.lastActivity > SESSION_TTL_MS) {
|
|
11524
|
-
try {
|
|
11525
|
-
await closeSession2(id);
|
|
11526
|
-
} catch {}
|
|
11527
|
-
}
|
|
11528
|
-
}
|
|
11529
|
-
}, 60000);
|
|
11530
|
-
if (ttlInterval.unref)
|
|
11531
|
-
ttlInterval.unref();
|
|
11532
|
-
});
|
|
11533
|
-
|
|
11534
11205
|
// src/lib/snapshot.ts
|
|
11535
11206
|
var exports_snapshot = {};
|
|
11536
11207
|
__export(exports_snapshot, {
|
|
@@ -11881,6 +11552,7 @@ __export(exports_actions, {
|
|
|
11881
11552
|
typeRef: () => typeRef,
|
|
11882
11553
|
type: () => type,
|
|
11883
11554
|
stopWatch: () => stopWatch,
|
|
11555
|
+
stopAllWatchesForSession: () => stopAllWatchesForSession,
|
|
11884
11556
|
selectRef: () => selectRef,
|
|
11885
11557
|
selectOption: () => selectOption,
|
|
11886
11558
|
scrollTo: () => scrollTo,
|
|
@@ -12048,238 +11720,601 @@ async function waitForSelector(page, selector, opts) {
|
|
|
12048
11720
|
throw new ElementNotFoundError(selector);
|
|
12049
11721
|
}
|
|
12050
11722
|
}
|
|
12051
|
-
async function waitForNavigation(page, timeout = 30000) {
|
|
11723
|
+
async function waitForNavigation(page, timeout = 30000) {
|
|
11724
|
+
try {
|
|
11725
|
+
await page.waitForLoadState("domcontentloaded", { timeout });
|
|
11726
|
+
} catch (err) {
|
|
11727
|
+
throw new NavigationError("navigation", err instanceof Error ? err.message : String(err));
|
|
11728
|
+
}
|
|
11729
|
+
}
|
|
11730
|
+
async function pressKey(page, key) {
|
|
11731
|
+
await page.keyboard.press(key);
|
|
11732
|
+
}
|
|
11733
|
+
async function withRetry(fn, opts) {
|
|
11734
|
+
const retries = opts?.retries ?? 2;
|
|
11735
|
+
const delay = opts?.delay ?? 300;
|
|
11736
|
+
const retryOn = opts?.retryOn ?? RETRYABLE_ERRORS;
|
|
11737
|
+
let lastErr;
|
|
11738
|
+
for (let attempt = 0;attempt <= retries; attempt++) {
|
|
11739
|
+
try {
|
|
11740
|
+
return await fn();
|
|
11741
|
+
} catch (err) {
|
|
11742
|
+
lastErr = err;
|
|
11743
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11744
|
+
const shouldRetry = retryOn.some((pattern) => msg.includes(pattern));
|
|
11745
|
+
if (!shouldRetry || err instanceof ElementNotFoundError)
|
|
11746
|
+
throw err;
|
|
11747
|
+
if (attempt < retries)
|
|
11748
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
11749
|
+
}
|
|
11750
|
+
}
|
|
11751
|
+
throw lastErr;
|
|
11752
|
+
}
|
|
11753
|
+
async function clickText(page, text, opts) {
|
|
11754
|
+
await withRetry(async () => {
|
|
11755
|
+
try {
|
|
11756
|
+
await page.getByText(text, { exact: opts?.exact ?? false }).first().click({ timeout: opts?.timeout ?? 1e4 });
|
|
11757
|
+
} catch (err) {
|
|
11758
|
+
throw new BrowserError(`clickText: could not find or click text "${text}": ${err instanceof Error ? err.message : String(err)}`, "CLICK_TEXT_FAILED");
|
|
11759
|
+
}
|
|
11760
|
+
}, { retries: opts?.retries ?? 1 });
|
|
11761
|
+
}
|
|
11762
|
+
async function fillForm(page, fields, submitSelector, selfHeal = true) {
|
|
11763
|
+
let filled = 0;
|
|
11764
|
+
const errors2 = [];
|
|
11765
|
+
const healedFields = [];
|
|
11766
|
+
for (const [selector, value] of Object.entries(fields)) {
|
|
11767
|
+
try {
|
|
11768
|
+
let el = await page.$(selector);
|
|
11769
|
+
if (!el && selfHeal) {
|
|
11770
|
+
const result = await healSelector(page, selector);
|
|
11771
|
+
if (result.found && result.locator) {
|
|
11772
|
+
const handle = await result.locator.elementHandle();
|
|
11773
|
+
if (handle) {
|
|
11774
|
+
el = handle;
|
|
11775
|
+
healedFields.push(selector);
|
|
11776
|
+
const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
|
|
11777
|
+
const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
|
|
11778
|
+
if (tagName2 === "select") {
|
|
11779
|
+
await result.locator.selectOption(String(value));
|
|
11780
|
+
} else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
|
|
11781
|
+
if (Boolean(value))
|
|
11782
|
+
await result.locator.check();
|
|
11783
|
+
else
|
|
11784
|
+
await result.locator.uncheck();
|
|
11785
|
+
} else {
|
|
11786
|
+
await result.locator.fill(String(value));
|
|
11787
|
+
}
|
|
11788
|
+
filled++;
|
|
11789
|
+
continue;
|
|
11790
|
+
}
|
|
11791
|
+
}
|
|
11792
|
+
errors2.push(`${selector}: element not found`);
|
|
11793
|
+
continue;
|
|
11794
|
+
}
|
|
11795
|
+
if (!el) {
|
|
11796
|
+
errors2.push(`${selector}: element not found`);
|
|
11797
|
+
continue;
|
|
11798
|
+
}
|
|
11799
|
+
const tagName = await el.evaluate((e) => e.tagName.toLowerCase());
|
|
11800
|
+
const inputType = await el.evaluate((e) => e.type?.toLowerCase() ?? "text");
|
|
11801
|
+
if (tagName === "select") {
|
|
11802
|
+
await page.selectOption(selector, String(value));
|
|
11803
|
+
} else if (tagName === "input" && (inputType === "checkbox" || inputType === "radio")) {
|
|
11804
|
+
const checked = Boolean(value);
|
|
11805
|
+
if (checked) {
|
|
11806
|
+
await page.check(selector);
|
|
11807
|
+
} else {
|
|
11808
|
+
await page.uncheck(selector);
|
|
11809
|
+
}
|
|
11810
|
+
} else {
|
|
11811
|
+
await page.fill(selector, String(value));
|
|
11812
|
+
}
|
|
11813
|
+
filled++;
|
|
11814
|
+
} catch (err) {
|
|
11815
|
+
errors2.push(`${selector}: ${err instanceof Error ? err.message : String(err)}`);
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
if (submitSelector) {
|
|
11819
|
+
try {
|
|
11820
|
+
await page.click(submitSelector);
|
|
11821
|
+
} catch (submitErr) {
|
|
11822
|
+
if (selfHeal) {
|
|
11823
|
+
const result = await healSelector(page, submitSelector);
|
|
11824
|
+
if (result.found && result.locator) {
|
|
11825
|
+
await result.locator.click();
|
|
11826
|
+
healedFields.push(submitSelector);
|
|
11827
|
+
} else {
|
|
11828
|
+
errors2.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
11829
|
+
}
|
|
11830
|
+
} else {
|
|
11831
|
+
errors2.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
11832
|
+
}
|
|
11833
|
+
}
|
|
11834
|
+
}
|
|
11835
|
+
return { filled, errors: errors2, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
|
|
11836
|
+
}
|
|
11837
|
+
async function waitForText(page, text, opts) {
|
|
11838
|
+
const timeout = opts?.timeout ?? 1e4;
|
|
11839
|
+
try {
|
|
11840
|
+
await page.getByText(text, { exact: opts?.exact ?? false }).first().waitFor({ state: "visible", timeout });
|
|
11841
|
+
} catch (err) {
|
|
11842
|
+
throw new ElementNotFoundError(`text:"${text}"`);
|
|
11843
|
+
}
|
|
11844
|
+
}
|
|
11845
|
+
function watchPage(page, opts) {
|
|
11846
|
+
const id = `watch-${Date.now()}`;
|
|
11847
|
+
const changes = [];
|
|
11848
|
+
const intervalMs = opts?.intervalMs ?? 500;
|
|
11849
|
+
const maxChanges = opts?.maxChanges ?? 50;
|
|
11850
|
+
const interval = setInterval(async () => {
|
|
11851
|
+
if (changes.length >= maxChanges)
|
|
11852
|
+
return;
|
|
11853
|
+
try {
|
|
11854
|
+
const change = await page.evaluate((sel) => {
|
|
11855
|
+
const el = sel ? document.querySelector(sel) : document.body;
|
|
11856
|
+
return el ? `${new Date().toISOString()}:${el.textContent?.slice(0, 100)}` : null;
|
|
11857
|
+
}, opts?.selector ?? null);
|
|
11858
|
+
if (change && (changes.length === 0 || changes[changes.length - 1] !== change)) {
|
|
11859
|
+
changes.push(change);
|
|
11860
|
+
}
|
|
11861
|
+
} catch {}
|
|
11862
|
+
}, intervalMs);
|
|
11863
|
+
activeWatches.set(id, { interval, changes });
|
|
11864
|
+
return {
|
|
11865
|
+
id,
|
|
11866
|
+
stop: () => {
|
|
11867
|
+
clearInterval(interval);
|
|
11868
|
+
activeWatches.delete(id);
|
|
11869
|
+
}
|
|
11870
|
+
};
|
|
11871
|
+
}
|
|
11872
|
+
function getWatchChanges(watchId) {
|
|
11873
|
+
return activeWatches.get(watchId)?.changes ?? [];
|
|
11874
|
+
}
|
|
11875
|
+
function stopWatch(watchId) {
|
|
11876
|
+
const w = activeWatches.get(watchId);
|
|
11877
|
+
if (w) {
|
|
11878
|
+
clearInterval(w.interval);
|
|
11879
|
+
activeWatches.delete(watchId);
|
|
11880
|
+
}
|
|
11881
|
+
}
|
|
11882
|
+
function stopAllWatchesForSession(_sessionId) {
|
|
11883
|
+
for (const [id, w] of activeWatches) {
|
|
11884
|
+
clearInterval(w.interval);
|
|
11885
|
+
activeWatches.delete(id);
|
|
11886
|
+
}
|
|
11887
|
+
}
|
|
11888
|
+
async function clickRef(page, sessionId, ref, opts) {
|
|
11889
|
+
try {
|
|
11890
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11891
|
+
await locator.click({ timeout: opts?.timeout ?? 1e4 });
|
|
11892
|
+
} catch (err) {
|
|
11893
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11894
|
+
throw new ElementNotFoundError(ref);
|
|
11895
|
+
if (err instanceof Error && err.message.includes("No snapshot"))
|
|
11896
|
+
throw new BrowserError(err.message, "NO_SNAPSHOT");
|
|
11897
|
+
throw new BrowserError(`clickRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CLICK_REF_FAILED");
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
async function typeRef(page, sessionId, ref, text, opts) {
|
|
11901
|
+
try {
|
|
11902
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11903
|
+
if (opts?.clear)
|
|
11904
|
+
await locator.fill("", { timeout: opts.timeout ?? 1e4 });
|
|
11905
|
+
await locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
11906
|
+
} catch (err) {
|
|
11907
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11908
|
+
throw new ElementNotFoundError(ref);
|
|
11909
|
+
throw new BrowserError(`typeRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "TYPE_REF_FAILED");
|
|
11910
|
+
}
|
|
11911
|
+
}
|
|
11912
|
+
async function fillRef(page, sessionId, ref, value, timeout = 1e4) {
|
|
11913
|
+
try {
|
|
11914
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11915
|
+
await locator.fill(value, { timeout });
|
|
11916
|
+
} catch (err) {
|
|
11917
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11918
|
+
throw new ElementNotFoundError(ref);
|
|
11919
|
+
throw new BrowserError(`fillRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "FILL_REF_FAILED");
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11922
|
+
async function selectRef(page, sessionId, ref, value, timeout = 1e4) {
|
|
11923
|
+
try {
|
|
11924
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11925
|
+
return await locator.selectOption(value, { timeout });
|
|
11926
|
+
} catch (err) {
|
|
11927
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11928
|
+
throw new ElementNotFoundError(ref);
|
|
11929
|
+
throw new BrowserError(`selectRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "SELECT_REF_FAILED");
|
|
11930
|
+
}
|
|
11931
|
+
}
|
|
11932
|
+
async function checkRef(page, sessionId, ref, checked, timeout = 1e4) {
|
|
11933
|
+
try {
|
|
11934
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11935
|
+
if (checked)
|
|
11936
|
+
await locator.check({ timeout });
|
|
11937
|
+
else
|
|
11938
|
+
await locator.uncheck({ timeout });
|
|
11939
|
+
} catch (err) {
|
|
11940
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11941
|
+
throw new ElementNotFoundError(ref);
|
|
11942
|
+
throw new BrowserError(`checkRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CHECK_REF_FAILED");
|
|
11943
|
+
}
|
|
11944
|
+
}
|
|
11945
|
+
async function hoverRef(page, sessionId, ref, timeout = 1e4) {
|
|
12052
11946
|
try {
|
|
12053
|
-
|
|
11947
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
11948
|
+
await locator.hover({ timeout });
|
|
12054
11949
|
} catch (err) {
|
|
12055
|
-
|
|
11950
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
11951
|
+
throw new ElementNotFoundError(ref);
|
|
11952
|
+
throw new BrowserError(`hoverRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "HOVER_REF_FAILED");
|
|
12056
11953
|
}
|
|
12057
11954
|
}
|
|
12058
|
-
|
|
12059
|
-
|
|
11955
|
+
var RETRYABLE_ERRORS, activeWatches;
|
|
11956
|
+
var init_actions = __esm(() => {
|
|
11957
|
+
init_types();
|
|
11958
|
+
init_snapshot();
|
|
11959
|
+
RETRYABLE_ERRORS = ["Timeout", "timeout", "navigation", "net::ERR", "Target closed"];
|
|
11960
|
+
activeWatches = new Map;
|
|
11961
|
+
});
|
|
11962
|
+
|
|
11963
|
+
// src/lib/session.ts
|
|
11964
|
+
var exports_session = {};
|
|
11965
|
+
__export(exports_session, {
|
|
11966
|
+
setSessionPage: () => setSessionPage,
|
|
11967
|
+
renameSession: () => renameSession2,
|
|
11968
|
+
listSessions: () => listSessions2,
|
|
11969
|
+
isBunSession: () => isBunSession,
|
|
11970
|
+
isAutoGallery: () => isAutoGallery,
|
|
11971
|
+
hasActiveHandle: () => hasActiveHandle,
|
|
11972
|
+
getTokenBudget: () => getTokenBudget,
|
|
11973
|
+
getSessionPage: () => getSessionPage,
|
|
11974
|
+
getSessionEngine: () => getSessionEngine,
|
|
11975
|
+
getSessionByName: () => getSessionByName2,
|
|
11976
|
+
getSessionBunView: () => getSessionBunView,
|
|
11977
|
+
getSessionBrowser: () => getSessionBrowser,
|
|
11978
|
+
getSession: () => getSession2,
|
|
11979
|
+
getDefaultSession: () => getDefaultSession,
|
|
11980
|
+
getActiveSessions: () => getActiveSessions,
|
|
11981
|
+
getActiveSessionForAgent: () => getActiveSessionForAgent2,
|
|
11982
|
+
createSession: () => createSession2,
|
|
11983
|
+
countActiveSessions: () => countActiveSessions2,
|
|
11984
|
+
closeSession: () => closeSession2,
|
|
11985
|
+
closeAllSessions: () => closeAllSessions,
|
|
11986
|
+
browserPool: () => pool
|
|
11987
|
+
});
|
|
11988
|
+
function createBunProxy(view) {
|
|
11989
|
+
return view;
|
|
12060
11990
|
}
|
|
12061
|
-
async function
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
|
|
11991
|
+
async function createSession2(opts = {}) {
|
|
11992
|
+
if (opts.cdpUrl) {
|
|
11993
|
+
const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
|
|
11994
|
+
const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
|
|
11995
|
+
const contexts = cdpBrowser.contexts();
|
|
11996
|
+
const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
|
|
11997
|
+
const pages = context.pages();
|
|
11998
|
+
const page2 = pages.length > 0 ? pages[0] : await context.newPage();
|
|
11999
|
+
const session2 = createSession({
|
|
12000
|
+
engine: "cdp",
|
|
12001
|
+
projectId: opts.projectId,
|
|
12002
|
+
agentId: opts.agentId,
|
|
12003
|
+
startUrl: page2.url(),
|
|
12004
|
+
name: opts.name ?? "attached"
|
|
12005
|
+
});
|
|
12006
|
+
const cleanups2 = [];
|
|
12007
|
+
if (opts.captureNetwork !== false) {
|
|
12008
|
+
try {
|
|
12009
|
+
cleanups2.push(enableNetworkLogging(page2, session2.id));
|
|
12010
|
+
} catch {}
|
|
12011
|
+
}
|
|
12012
|
+
if (opts.captureConsole !== false) {
|
|
12013
|
+
try {
|
|
12014
|
+
cleanups2.push(enableConsoleCapture(page2, session2.id));
|
|
12015
|
+
} catch {}
|
|
12016
|
+
}
|
|
12067
12017
|
try {
|
|
12068
|
-
|
|
12069
|
-
} catch
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12018
|
+
cleanups2.push(setupDialogHandler(page2, session2.id));
|
|
12019
|
+
} catch {}
|
|
12020
|
+
handles.set(session2.id, { browser: cdpBrowser, bunView: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
12021
|
+
return { session: session2, page: page2 };
|
|
12022
|
+
}
|
|
12023
|
+
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
12024
|
+
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
12025
|
+
let browser = null;
|
|
12026
|
+
let bunView = null;
|
|
12027
|
+
let page;
|
|
12028
|
+
if (resolvedEngine === "bun") {
|
|
12029
|
+
if (!isBunWebViewAvailable()) {
|
|
12030
|
+
console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
|
|
12031
|
+
browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
|
|
12032
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
12033
|
+
} else {
|
|
12034
|
+
bunView = new BunWebViewSession({
|
|
12035
|
+
width: opts.viewport?.width ?? 1280,
|
|
12036
|
+
height: opts.viewport?.height ?? 720,
|
|
12037
|
+
profile: opts.name ?? undefined
|
|
12038
|
+
});
|
|
12039
|
+
if (opts.stealth) {}
|
|
12040
|
+
page = createBunProxy(bunView);
|
|
12041
|
+
}
|
|
12042
|
+
} else if (resolvedEngine === "lightpanda") {
|
|
12043
|
+
browser = await connectLightpanda();
|
|
12044
|
+
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
12045
|
+
page = await context.newPage();
|
|
12046
|
+
} else {
|
|
12047
|
+
browser = await pool.acquire(opts.headless ?? true);
|
|
12048
|
+
if (opts.storageState) {
|
|
12049
|
+
const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
12050
|
+
const statePath2 = loadStatePath2(opts.storageState);
|
|
12051
|
+
if (statePath2) {
|
|
12052
|
+
const context = await browser.newContext({
|
|
12053
|
+
viewport: opts.viewport ?? { width: 1280, height: 720 },
|
|
12054
|
+
userAgent: opts.userAgent,
|
|
12055
|
+
storageState: statePath2
|
|
12056
|
+
});
|
|
12057
|
+
page = await context.newPage();
|
|
12058
|
+
} else {
|
|
12059
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
12060
|
+
}
|
|
12061
|
+
} else {
|
|
12062
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
12077
12063
|
}
|
|
12078
12064
|
}
|
|
12079
|
-
|
|
12080
|
-
}
|
|
12081
|
-
async function clickText(page, text, opts) {
|
|
12082
|
-
await withRetry(async () => {
|
|
12065
|
+
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
12083
12066
|
try {
|
|
12084
|
-
|
|
12085
|
-
} catch
|
|
12086
|
-
|
|
12067
|
+
return new URL(opts.startUrl).hostname;
|
|
12068
|
+
} catch {
|
|
12069
|
+
return;
|
|
12087
12070
|
}
|
|
12088
|
-
}
|
|
12089
|
-
|
|
12090
|
-
|
|
12091
|
-
|
|
12092
|
-
|
|
12093
|
-
|
|
12094
|
-
|
|
12071
|
+
})() : undefined);
|
|
12072
|
+
const session = createSession({
|
|
12073
|
+
engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
|
|
12074
|
+
projectId: opts.projectId,
|
|
12075
|
+
agentId: opts.agentId,
|
|
12076
|
+
startUrl: opts.startUrl,
|
|
12077
|
+
name: sessionName
|
|
12078
|
+
});
|
|
12079
|
+
if (opts.stealth && !bunView) {
|
|
12095
12080
|
try {
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
|
|
12104
|
-
|
|
12105
|
-
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
|
|
12114
|
-
|
|
12115
|
-
|
|
12116
|
-
|
|
12117
|
-
|
|
12118
|
-
|
|
12119
|
-
|
|
12120
|
-
|
|
12121
|
-
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
|
|
12126
|
-
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
if (tagName === "select") {
|
|
12130
|
-
await page.selectOption(selector, String(value));
|
|
12131
|
-
} else if (tagName === "input" && (inputType === "checkbox" || inputType === "radio")) {
|
|
12132
|
-
const checked = Boolean(value);
|
|
12133
|
-
if (checked) {
|
|
12134
|
-
await page.check(selector);
|
|
12135
|
-
} else {
|
|
12136
|
-
await page.uncheck(selector);
|
|
12137
|
-
}
|
|
12138
|
-
} else {
|
|
12139
|
-
await page.fill(selector, String(value));
|
|
12140
|
-
}
|
|
12141
|
-
filled++;
|
|
12142
|
-
} catch (err) {
|
|
12143
|
-
errors2.push(`${selector}: ${err instanceof Error ? err.message : String(err)}`);
|
|
12081
|
+
await applyStealthPatches(page);
|
|
12082
|
+
} catch {}
|
|
12083
|
+
}
|
|
12084
|
+
const cleanups = [];
|
|
12085
|
+
if (!bunView) {
|
|
12086
|
+
if (opts.captureNetwork !== false) {
|
|
12087
|
+
try {
|
|
12088
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
12089
|
+
} catch {}
|
|
12090
|
+
}
|
|
12091
|
+
if (opts.captureConsole !== false) {
|
|
12092
|
+
try {
|
|
12093
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
12094
|
+
} catch {}
|
|
12095
|
+
}
|
|
12096
|
+
try {
|
|
12097
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
12098
|
+
} catch {}
|
|
12099
|
+
} else {
|
|
12100
|
+
if (opts.captureConsole !== false) {
|
|
12101
|
+
try {
|
|
12102
|
+
const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
12103
|
+
await bunView.addInitScript(`
|
|
12104
|
+
(() => {
|
|
12105
|
+
const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
|
|
12106
|
+
['log','warn','error','debug','info'].forEach(level => {
|
|
12107
|
+
console[level] = (...args) => {
|
|
12108
|
+
orig[level](...args);
|
|
12109
|
+
};
|
|
12110
|
+
});
|
|
12111
|
+
})()
|
|
12112
|
+
`);
|
|
12113
|
+
} catch {}
|
|
12144
12114
|
}
|
|
12145
12115
|
}
|
|
12146
|
-
|
|
12116
|
+
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
12117
|
+
if (opts.startUrl) {
|
|
12147
12118
|
try {
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
if (selfHeal) {
|
|
12151
|
-
const result = await healSelector(page, submitSelector);
|
|
12152
|
-
if (result.found && result.locator) {
|
|
12153
|
-
await result.locator.click();
|
|
12154
|
-
healedFields.push(submitSelector);
|
|
12155
|
-
} else {
|
|
12156
|
-
errors2.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
12157
|
-
}
|
|
12119
|
+
if (bunView) {
|
|
12120
|
+
await bunView.goto(opts.startUrl);
|
|
12158
12121
|
} else {
|
|
12159
|
-
|
|
12122
|
+
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
12160
12123
|
}
|
|
12161
|
-
}
|
|
12124
|
+
} catch {}
|
|
12162
12125
|
}
|
|
12163
|
-
return {
|
|
12126
|
+
return { session, page };
|
|
12164
12127
|
}
|
|
12165
|
-
|
|
12166
|
-
const
|
|
12128
|
+
function getSessionPage(sessionId) {
|
|
12129
|
+
const handle = handles.get(sessionId);
|
|
12130
|
+
if (!handle)
|
|
12131
|
+
throw new SessionNotFoundError(sessionId);
|
|
12167
12132
|
try {
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12133
|
+
if (handle.bunView) {
|
|
12134
|
+
handle.bunView.url();
|
|
12135
|
+
} else {
|
|
12136
|
+
handle.page.url();
|
|
12137
|
+
}
|
|
12138
|
+
} catch {
|
|
12139
|
+
handles.delete(sessionId);
|
|
12140
|
+
throw new SessionNotFoundError(sessionId);
|
|
12171
12141
|
}
|
|
12142
|
+
handle.lastActivity = Date.now();
|
|
12143
|
+
return handle.page;
|
|
12172
12144
|
}
|
|
12173
|
-
function
|
|
12174
|
-
|
|
12175
|
-
const changes = [];
|
|
12176
|
-
const intervalMs = opts?.intervalMs ?? 500;
|
|
12177
|
-
const maxChanges = opts?.maxChanges ?? 50;
|
|
12178
|
-
const interval = setInterval(async () => {
|
|
12179
|
-
if (changes.length >= maxChanges)
|
|
12180
|
-
return;
|
|
12181
|
-
try {
|
|
12182
|
-
const change = await page.evaluate((sel) => {
|
|
12183
|
-
const el = sel ? document.querySelector(sel) : document.body;
|
|
12184
|
-
return el ? `${new Date().toISOString()}:${el.textContent?.slice(0, 100)}` : null;
|
|
12185
|
-
}, opts?.selector ?? null);
|
|
12186
|
-
if (change && (changes.length === 0 || changes[changes.length - 1] !== change)) {
|
|
12187
|
-
changes.push(change);
|
|
12188
|
-
}
|
|
12189
|
-
} catch {}
|
|
12190
|
-
}, intervalMs);
|
|
12191
|
-
activeWatches.set(id, { interval, changes });
|
|
12192
|
-
return {
|
|
12193
|
-
id,
|
|
12194
|
-
stop: () => {
|
|
12195
|
-
clearInterval(interval);
|
|
12196
|
-
activeWatches.delete(id);
|
|
12197
|
-
}
|
|
12198
|
-
};
|
|
12145
|
+
function getSessionBunView(sessionId) {
|
|
12146
|
+
return handles.get(sessionId)?.bunView ?? null;
|
|
12199
12147
|
}
|
|
12200
|
-
function
|
|
12201
|
-
return
|
|
12148
|
+
function isBunSession(sessionId) {
|
|
12149
|
+
return handles.get(sessionId)?.engine === "bun";
|
|
12202
12150
|
}
|
|
12203
|
-
function
|
|
12204
|
-
const
|
|
12205
|
-
if (
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12151
|
+
function getSessionBrowser(sessionId) {
|
|
12152
|
+
const handle = handles.get(sessionId);
|
|
12153
|
+
if (!handle)
|
|
12154
|
+
throw new SessionNotFoundError(sessionId);
|
|
12155
|
+
if (!handle.browser)
|
|
12156
|
+
throw new BrowserError("This session uses Bun.WebView (no Playwright browser)", "NO_PLAYWRIGHT_BROWSER");
|
|
12157
|
+
return handle.browser;
|
|
12209
12158
|
}
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12213
|
-
|
|
12214
|
-
|
|
12215
|
-
if (err instanceof Error && err.message.includes("Ref "))
|
|
12216
|
-
throw new ElementNotFoundError(ref);
|
|
12217
|
-
if (err instanceof Error && err.message.includes("No snapshot"))
|
|
12218
|
-
throw new BrowserError(err.message, "NO_SNAPSHOT");
|
|
12219
|
-
throw new BrowserError(`clickRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CLICK_REF_FAILED");
|
|
12220
|
-
}
|
|
12159
|
+
function getSessionEngine(sessionId) {
|
|
12160
|
+
const handle = handles.get(sessionId);
|
|
12161
|
+
if (!handle)
|
|
12162
|
+
throw new SessionNotFoundError(sessionId);
|
|
12163
|
+
return handle.engine;
|
|
12221
12164
|
}
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
const locator = getRefLocator(page, sessionId, ref);
|
|
12225
|
-
if (opts?.clear)
|
|
12226
|
-
await locator.fill("", { timeout: opts.timeout ?? 1e4 });
|
|
12227
|
-
await locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
12228
|
-
} catch (err) {
|
|
12229
|
-
if (err instanceof Error && err.message.includes("Ref "))
|
|
12230
|
-
throw new ElementNotFoundError(ref);
|
|
12231
|
-
throw new BrowserError(`typeRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "TYPE_REF_FAILED");
|
|
12232
|
-
}
|
|
12165
|
+
function hasActiveHandle(sessionId) {
|
|
12166
|
+
return handles.has(sessionId);
|
|
12233
12167
|
}
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
if (err instanceof Error && err.message.includes("Ref "))
|
|
12240
|
-
throw new ElementNotFoundError(ref);
|
|
12241
|
-
throw new BrowserError(`fillRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "FILL_REF_FAILED");
|
|
12242
|
-
}
|
|
12168
|
+
function setSessionPage(sessionId, page) {
|
|
12169
|
+
const handle = handles.get(sessionId);
|
|
12170
|
+
if (!handle)
|
|
12171
|
+
throw new SessionNotFoundError(sessionId);
|
|
12172
|
+
handle.page = page;
|
|
12243
12173
|
}
|
|
12244
|
-
async function
|
|
12174
|
+
async function closeSession2(sessionId) {
|
|
12175
|
+
const handle = handles.get(sessionId);
|
|
12176
|
+
if (handle) {
|
|
12177
|
+
for (const cleanup of handle.cleanups) {
|
|
12178
|
+
try {
|
|
12179
|
+
cleanup();
|
|
12180
|
+
} catch {}
|
|
12181
|
+
}
|
|
12182
|
+
if (handle.bunView) {
|
|
12183
|
+
try {
|
|
12184
|
+
await handle.bunView.close();
|
|
12185
|
+
} catch {}
|
|
12186
|
+
} else {
|
|
12187
|
+
try {
|
|
12188
|
+
await handle.page.context().close();
|
|
12189
|
+
} catch {}
|
|
12190
|
+
if (handle.browser)
|
|
12191
|
+
pool.release(handle.browser);
|
|
12192
|
+
}
|
|
12193
|
+
handles.delete(sessionId);
|
|
12194
|
+
}
|
|
12245
12195
|
try {
|
|
12246
|
-
const
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12196
|
+
const { clearLastSnapshot: clearLastSnapshot2, clearSessionRefs: clearSessionRefs2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
12197
|
+
clearLastSnapshot2(sessionId);
|
|
12198
|
+
clearSessionRefs2(sessionId);
|
|
12199
|
+
} catch {}
|
|
12200
|
+
try {
|
|
12201
|
+
const { stopAllWatchesForSession: stopAllWatchesForSession2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
12202
|
+
stopAllWatchesForSession2(sessionId);
|
|
12203
|
+
} catch {}
|
|
12204
|
+
try {
|
|
12205
|
+
const { clearDialogs: clearDialogs2 } = await Promise.resolve().then(() => (init_dialogs(), exports_dialogs));
|
|
12206
|
+
clearDialogs2(sessionId);
|
|
12207
|
+
} catch {}
|
|
12208
|
+
return closeSession(sessionId);
|
|
12209
|
+
}
|
|
12210
|
+
function getSession2(sessionId) {
|
|
12211
|
+
return getSession(sessionId);
|
|
12212
|
+
}
|
|
12213
|
+
function listSessions2(filter) {
|
|
12214
|
+
return listSessions(filter);
|
|
12215
|
+
}
|
|
12216
|
+
function getActiveSessions() {
|
|
12217
|
+
return listSessions({ status: "active" });
|
|
12218
|
+
}
|
|
12219
|
+
async function closeAllSessions() {
|
|
12220
|
+
for (const [id] of handles) {
|
|
12221
|
+
await closeSession2(id).catch(() => {});
|
|
12252
12222
|
}
|
|
12223
|
+
await pool.destroyAll();
|
|
12253
12224
|
}
|
|
12254
|
-
|
|
12225
|
+
function getSessionByName2(name) {
|
|
12226
|
+
return getSessionByName(name);
|
|
12227
|
+
}
|
|
12228
|
+
function renameSession2(id, name) {
|
|
12229
|
+
return renameSession(id, name);
|
|
12230
|
+
}
|
|
12231
|
+
function getTokenBudget(sessionId) {
|
|
12232
|
+
const handle = handles.get(sessionId);
|
|
12233
|
+
return handle ? handle.tokenBudget : null;
|
|
12234
|
+
}
|
|
12235
|
+
function getActiveSessionForAgent2(agentId) {
|
|
12236
|
+
const session = getActiveSessionForAgent(agentId);
|
|
12237
|
+
if (!session)
|
|
12238
|
+
return null;
|
|
12239
|
+
const handle = handles.get(session.id);
|
|
12240
|
+
if (!handle)
|
|
12241
|
+
return null;
|
|
12255
12242
|
try {
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
await locator.check({ timeout });
|
|
12243
|
+
if (handle.bunView)
|
|
12244
|
+
handle.bunView.url();
|
|
12259
12245
|
else
|
|
12260
|
-
|
|
12261
|
-
} catch
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
throw new BrowserError(`checkRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CHECK_REF_FAILED");
|
|
12246
|
+
handle.page.url();
|
|
12247
|
+
} catch {
|
|
12248
|
+
handles.delete(session.id);
|
|
12249
|
+
return null;
|
|
12265
12250
|
}
|
|
12251
|
+
return { session, page: handle.page };
|
|
12266
12252
|
}
|
|
12267
|
-
|
|
12253
|
+
function getDefaultSession() {
|
|
12254
|
+
const session = getDefaultActiveSession();
|
|
12255
|
+
if (!session)
|
|
12256
|
+
return null;
|
|
12257
|
+
const handle = handles.get(session.id);
|
|
12258
|
+
if (!handle)
|
|
12259
|
+
return null;
|
|
12268
12260
|
try {
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12261
|
+
if (handle.bunView)
|
|
12262
|
+
handle.bunView.url();
|
|
12263
|
+
else
|
|
12264
|
+
handle.page.url();
|
|
12265
|
+
} catch {
|
|
12266
|
+
handles.delete(session.id);
|
|
12267
|
+
return null;
|
|
12275
12268
|
}
|
|
12269
|
+
return { session, page: handle.page };
|
|
12276
12270
|
}
|
|
12277
|
-
|
|
12278
|
-
|
|
12271
|
+
function isAutoGallery(sessionId) {
|
|
12272
|
+
return handles.get(sessionId)?.autoGallery ?? false;
|
|
12273
|
+
}
|
|
12274
|
+
function countActiveSessions2() {
|
|
12275
|
+
return countActiveSessions();
|
|
12276
|
+
}
|
|
12277
|
+
var handles, pool, SESSION_TTL_MS, ttlInterval, DB_PRUNE_INTERVAL_MS, DB_RETENTION_HOURS = 24, dbPruneInterval;
|
|
12278
|
+
var init_session = __esm(() => {
|
|
12279
12279
|
init_types();
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12280
|
+
init_types();
|
|
12281
|
+
init_sessions();
|
|
12282
|
+
init_playwright();
|
|
12283
|
+
init_lightpanda();
|
|
12284
|
+
init_bun_webview();
|
|
12285
|
+
init_selector();
|
|
12286
|
+
init_network();
|
|
12287
|
+
init_console();
|
|
12288
|
+
init_stealth();
|
|
12289
|
+
init_dialogs();
|
|
12290
|
+
handles = new Map;
|
|
12291
|
+
pool = new BrowserPool(5);
|
|
12292
|
+
SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
|
|
12293
|
+
ttlInterval = setInterval(async () => {
|
|
12294
|
+
const now = Date.now();
|
|
12295
|
+
for (const [id, handle] of handles) {
|
|
12296
|
+
if (now - handle.lastActivity > SESSION_TTL_MS) {
|
|
12297
|
+
try {
|
|
12298
|
+
await closeSession2(id);
|
|
12299
|
+
} catch {}
|
|
12300
|
+
}
|
|
12301
|
+
}
|
|
12302
|
+
}, 60000);
|
|
12303
|
+
if (ttlInterval.unref)
|
|
12304
|
+
ttlInterval.unref();
|
|
12305
|
+
DB_PRUNE_INTERVAL_MS = 30 * 60000;
|
|
12306
|
+
dbPruneInterval = setInterval(() => {
|
|
12307
|
+
try {
|
|
12308
|
+
const { getDatabase: getDatabase2 } = (init_schema(), __toCommonJS(exports_schema));
|
|
12309
|
+
const db = getDatabase2();
|
|
12310
|
+
const cutoff = new Date(Date.now() - DB_RETENTION_HOURS * 3600000).toISOString();
|
|
12311
|
+
db.prepare("DELETE FROM network_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
12312
|
+
db.prepare("DELETE FROM console_log WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
12313
|
+
db.prepare("DELETE FROM snapshots WHERE session_id IN (SELECT id FROM sessions WHERE status != 'active') AND timestamp < ?").run(cutoff);
|
|
12314
|
+
} catch {}
|
|
12315
|
+
}, DB_PRUNE_INTERVAL_MS);
|
|
12316
|
+
if (dbPruneInterval.unref)
|
|
12317
|
+
dbPruneInterval.unref();
|
|
12283
12318
|
});
|
|
12284
12319
|
|
|
12285
12320
|
// src/lib/extractor.ts
|
|
@@ -28477,6 +28512,11 @@ async function rememberPage(url, facts, tags) {
|
|
|
28477
28512
|
return;
|
|
28478
28513
|
} catch {}
|
|
28479
28514
|
}
|
|
28515
|
+
if (inMemoryCache.size >= MEMORY_MAX_SIZE && !inMemoryCache.has(key)) {
|
|
28516
|
+
const firstKey = inMemoryCache.keys().next().value;
|
|
28517
|
+
if (firstKey)
|
|
28518
|
+
inMemoryCache.delete(firstKey);
|
|
28519
|
+
}
|
|
28480
28520
|
inMemoryCache.set(key, {
|
|
28481
28521
|
data: memory,
|
|
28482
28522
|
expires: Date.now() + DEFAULT_TTL_HOURS * 60 * 60 * 1000
|
|
@@ -28506,9 +28546,18 @@ async function forgetPage(url) {
|
|
|
28506
28546
|
const key = cacheKey(url);
|
|
28507
28547
|
inMemoryCache.delete(key);
|
|
28508
28548
|
}
|
|
28509
|
-
var MEMORY_KEY_PREFIX = "browser-page:", DEFAULT_TTL_HOURS = 24, inMemoryCache;
|
|
28549
|
+
var MEMORY_KEY_PREFIX = "browser-page:", DEFAULT_TTL_HOURS = 24, inMemoryCache, MEMORY_MAX_SIZE = 200, _memorySweeper;
|
|
28510
28550
|
var init_page_memory = __esm(() => {
|
|
28511
28551
|
inMemoryCache = new Map;
|
|
28552
|
+
_memorySweeper = setInterval(() => {
|
|
28553
|
+
const now2 = Date.now();
|
|
28554
|
+
for (const [key, entry] of inMemoryCache) {
|
|
28555
|
+
if (entry.expires <= now2)
|
|
28556
|
+
inMemoryCache.delete(key);
|
|
28557
|
+
}
|
|
28558
|
+
}, 300000);
|
|
28559
|
+
if (_memorySweeper.unref)
|
|
28560
|
+
_memorySweeper.unref();
|
|
28512
28561
|
});
|
|
28513
28562
|
|
|
28514
28563
|
// node_modules/@hasna/conversations/dist/index.js
|
|
@@ -32972,6 +33021,11 @@ async function announceNavigation(url, sessionId, agentName = "browser-agent") {
|
|
|
32972
33021
|
await sdk.sendMessage(SPACE_NAME, `\uD83C\uDF10 Navigating to ${hostname} (session: ${sessionId.slice(0, 8)})`);
|
|
32973
33022
|
} catch {}
|
|
32974
33023
|
}
|
|
33024
|
+
if (activeNavigations.size >= NAV_MAX_SIZE && !activeNavigations.has(hostname)) {
|
|
33025
|
+
const firstKey = activeNavigations.keys().next().value;
|
|
33026
|
+
if (firstKey)
|
|
33027
|
+
activeNavigations.delete(firstKey);
|
|
33028
|
+
}
|
|
32975
33029
|
activeNavigations.set(hostname, { agentName, timestamp: Date.now() });
|
|
32976
33030
|
}
|
|
32977
33031
|
async function checkDuplicate2(url) {
|
|
@@ -33007,10 +33061,19 @@ async function checkDuplicate2(url) {
|
|
|
33007
33061
|
}
|
|
33008
33062
|
return { is_duplicate: false };
|
|
33009
33063
|
}
|
|
33010
|
-
var SPACE_NAME = "browser", DUPLICATE_WINDOW_MS, activeNavigations;
|
|
33064
|
+
var SPACE_NAME = "browser", DUPLICATE_WINDOW_MS, activeNavigations, NAV_MAX_SIZE = 200, _navSweeper;
|
|
33011
33065
|
var init_coordination = __esm(() => {
|
|
33012
33066
|
DUPLICATE_WINDOW_MS = 5 * 60 * 1000;
|
|
33013
33067
|
activeNavigations = new Map;
|
|
33068
|
+
_navSweeper = setInterval(() => {
|
|
33069
|
+
const cutoff = Date.now() - DUPLICATE_WINDOW_MS;
|
|
33070
|
+
for (const [key, entry] of activeNavigations) {
|
|
33071
|
+
if (entry.timestamp < cutoff)
|
|
33072
|
+
activeNavigations.delete(key);
|
|
33073
|
+
}
|
|
33074
|
+
}, 120000);
|
|
33075
|
+
if (_navSweeper.unref)
|
|
33076
|
+
_navSweeper.unref();
|
|
33014
33077
|
});
|
|
33015
33078
|
|
|
33016
33079
|
// node_modules/@hasna/todos/dist/index.js
|
|
@@ -38613,6 +38676,8 @@ Skill: ${task.skill}` : ""}`,
|
|
|
38613
38676
|
};
|
|
38614
38677
|
} catch {}
|
|
38615
38678
|
}
|
|
38679
|
+
if (inMemoryQueue.length >= QUEUE_MAX_SIZE)
|
|
38680
|
+
inMemoryQueue.shift();
|
|
38616
38681
|
const id = `btask-${Date.now()}`;
|
|
38617
38682
|
const entry = { ...task, id, status: "pending", created_at: new Date().toISOString() };
|
|
38618
38683
|
inMemoryQueue.push(entry);
|
|
@@ -38646,7 +38711,7 @@ async function completeBrowserTask(taskId, result) {
|
|
|
38646
38711
|
if (idx >= 0)
|
|
38647
38712
|
inMemoryQueue.splice(idx, 1);
|
|
38648
38713
|
}
|
|
38649
|
-
var inMemoryQueue;
|
|
38714
|
+
var QUEUE_MAX_SIZE = 100, inMemoryQueue;
|
|
38650
38715
|
var init_task_queue = __esm(() => {
|
|
38651
38716
|
inMemoryQueue = [];
|
|
38652
38717
|
});
|