@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/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.push(entry);
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
- await page.waitForLoadState("domcontentloaded", { timeout });
11947
+ const locator = getRefLocator(page, sessionId, ref);
11948
+ await locator.hover({ timeout });
12054
11949
  } catch (err) {
12055
- throw new NavigationError("navigation", err instanceof Error ? err.message : String(err));
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
- async function pressKey(page, key) {
12059
- await page.keyboard.press(key);
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 withRetry(fn, opts) {
12062
- const retries = opts?.retries ?? 2;
12063
- const delay = opts?.delay ?? 300;
12064
- const retryOn = opts?.retryOn ?? RETRYABLE_ERRORS;
12065
- let lastErr;
12066
- for (let attempt = 0;attempt <= retries; attempt++) {
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
- return await fn();
12069
- } catch (err) {
12070
- lastErr = err;
12071
- const msg = err instanceof Error ? err.message : String(err);
12072
- const shouldRetry = retryOn.some((pattern) => msg.includes(pattern));
12073
- if (!shouldRetry || err instanceof ElementNotFoundError)
12074
- throw err;
12075
- if (attempt < retries)
12076
- await new Promise((r) => setTimeout(r, delay));
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
- throw lastErr;
12080
- }
12081
- async function clickText(page, text, opts) {
12082
- await withRetry(async () => {
12065
+ const sessionName = opts.name ?? (opts.startUrl ? (() => {
12083
12066
  try {
12084
- await page.getByText(text, { exact: opts?.exact ?? false }).first().click({ timeout: opts?.timeout ?? 1e4 });
12085
- } catch (err) {
12086
- throw new BrowserError(`clickText: could not find or click text "${text}": ${err instanceof Error ? err.message : String(err)}`, "CLICK_TEXT_FAILED");
12067
+ return new URL(opts.startUrl).hostname;
12068
+ } catch {
12069
+ return;
12087
12070
  }
12088
- }, { retries: opts?.retries ?? 1 });
12089
- }
12090
- async function fillForm(page, fields, submitSelector, selfHeal = true) {
12091
- let filled = 0;
12092
- const errors2 = [];
12093
- const healedFields = [];
12094
- for (const [selector, value] of Object.entries(fields)) {
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
- let el = await page.$(selector);
12097
- if (!el && selfHeal) {
12098
- const result = await healSelector(page, selector);
12099
- if (result.found && result.locator) {
12100
- const handle = await result.locator.elementHandle();
12101
- if (handle) {
12102
- el = handle;
12103
- healedFields.push(selector);
12104
- const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
12105
- const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
12106
- if (tagName2 === "select") {
12107
- await result.locator.selectOption(String(value));
12108
- } else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
12109
- if (Boolean(value))
12110
- await result.locator.check();
12111
- else
12112
- await result.locator.uncheck();
12113
- } else {
12114
- await result.locator.fill(String(value));
12115
- }
12116
- filled++;
12117
- continue;
12118
- }
12119
- }
12120
- errors2.push(`${selector}: element not found`);
12121
- continue;
12122
- }
12123
- if (!el) {
12124
- errors2.push(`${selector}: element not found`);
12125
- continue;
12126
- }
12127
- const tagName = await el.evaluate((e) => e.tagName.toLowerCase());
12128
- const inputType = await el.evaluate((e) => e.type?.toLowerCase() ?? "text");
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
- if (submitSelector) {
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
- await page.click(submitSelector);
12149
- } catch (submitErr) {
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
- errors2.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
12122
+ await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
12160
12123
  }
12161
- }
12124
+ } catch {}
12162
12125
  }
12163
- return { filled, errors: errors2, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
12126
+ return { session, page };
12164
12127
  }
12165
- async function waitForText(page, text, opts) {
12166
- const timeout = opts?.timeout ?? 1e4;
12128
+ function getSessionPage(sessionId) {
12129
+ const handle = handles.get(sessionId);
12130
+ if (!handle)
12131
+ throw new SessionNotFoundError(sessionId);
12167
12132
  try {
12168
- await page.getByText(text, { exact: opts?.exact ?? false }).first().waitFor({ state: "visible", timeout });
12169
- } catch (err) {
12170
- throw new ElementNotFoundError(`text:"${text}"`);
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 watchPage(page, opts) {
12174
- const id = `watch-${Date.now()}`;
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 getWatchChanges(watchId) {
12201
- return activeWatches.get(watchId)?.changes ?? [];
12148
+ function isBunSession(sessionId) {
12149
+ return handles.get(sessionId)?.engine === "bun";
12202
12150
  }
12203
- function stopWatch(watchId) {
12204
- const w = activeWatches.get(watchId);
12205
- if (w) {
12206
- clearInterval(w.interval);
12207
- activeWatches.delete(watchId);
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
- async function clickRef(page, sessionId, ref, opts) {
12211
- try {
12212
- const locator = getRefLocator(page, sessionId, ref);
12213
- await locator.click({ timeout: opts?.timeout ?? 1e4 });
12214
- } catch (err) {
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
- async function typeRef(page, sessionId, ref, text, opts) {
12223
- try {
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
- async function fillRef(page, sessionId, ref, value, timeout = 1e4) {
12235
- try {
12236
- const locator = getRefLocator(page, sessionId, ref);
12237
- await locator.fill(value, { timeout });
12238
- } catch (err) {
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 selectRef(page, sessionId, ref, value, timeout = 1e4) {
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 locator = getRefLocator(page, sessionId, ref);
12247
- return await locator.selectOption(value, { timeout });
12248
- } catch (err) {
12249
- if (err instanceof Error && err.message.includes("Ref "))
12250
- throw new ElementNotFoundError(ref);
12251
- throw new BrowserError(`selectRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "SELECT_REF_FAILED");
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
- async function checkRef(page, sessionId, ref, checked, timeout = 1e4) {
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
- const locator = getRefLocator(page, sessionId, ref);
12257
- if (checked)
12258
- await locator.check({ timeout });
12243
+ if (handle.bunView)
12244
+ handle.bunView.url();
12259
12245
  else
12260
- await locator.uncheck({ timeout });
12261
- } catch (err) {
12262
- if (err instanceof Error && err.message.includes("Ref "))
12263
- throw new ElementNotFoundError(ref);
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
- async function hoverRef(page, sessionId, ref, timeout = 1e4) {
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
- const locator = getRefLocator(page, sessionId, ref);
12270
- await locator.hover({ timeout });
12271
- } catch (err) {
12272
- if (err instanceof Error && err.message.includes("Ref "))
12273
- throw new ElementNotFoundError(ref);
12274
- throw new BrowserError(`hoverRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "HOVER_REF_FAILED");
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
- var RETRYABLE_ERRORS, activeWatches;
12278
- var init_actions = __esm(() => {
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
- init_snapshot();
12281
- RETRYABLE_ERRORS = ["Timeout", "timeout", "navigation", "net::ERR", "Target closed"];
12282
- activeWatches = new Map;
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
  });