@hasna/browser 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28,6 +28,79 @@ var __export = (target, all) => {
28
28
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
29
  var __require = import.meta.require;
30
30
 
31
+ // src/types/index.ts
32
+ var UseCase, BrowserError, SessionNotFoundError, EngineNotAvailableError, NavigationError, ElementNotFoundError, RecordingNotFoundError, AgentNotFoundError, ProjectNotFoundError;
33
+ var init_types = __esm(() => {
34
+ ((UseCase2) => {
35
+ UseCase2["SCRAPE"] = "scrape";
36
+ UseCase2["EXTRACT_LINKS"] = "extract_links";
37
+ UseCase2["STATUS_CHECK"] = "status_check";
38
+ UseCase2["FORM_FILL"] = "form_fill";
39
+ UseCase2["SPA_NAVIGATE"] = "spa_navigate";
40
+ UseCase2["SCREENSHOT"] = "screenshot";
41
+ UseCase2["AUTH_FLOW"] = "auth_flow";
42
+ UseCase2["MULTI_TAB"] = "multi_tab";
43
+ UseCase2["NETWORK_MONITOR"] = "network_monitor";
44
+ UseCase2["HAR_CAPTURE"] = "har_capture";
45
+ UseCase2["PERF_PROFILE"] = "perf_profile";
46
+ UseCase2["SCRIPT_INJECT"] = "script_inject";
47
+ UseCase2["COVERAGE"] = "coverage";
48
+ UseCase2["RECORD_REPLAY"] = "record_replay";
49
+ })(UseCase ||= {});
50
+ BrowserError = class BrowserError extends Error {
51
+ code;
52
+ retryable;
53
+ constructor(message, code = "BROWSER_ERROR", retryable = false) {
54
+ super(message);
55
+ this.code = code;
56
+ this.retryable = retryable;
57
+ this.name = "BrowserError";
58
+ }
59
+ };
60
+ SessionNotFoundError = class SessionNotFoundError extends BrowserError {
61
+ constructor(id) {
62
+ super(`Session not found: ${id}`, "SESSION_NOT_FOUND", false);
63
+ this.name = "SessionNotFoundError";
64
+ }
65
+ };
66
+ EngineNotAvailableError = class EngineNotAvailableError extends BrowserError {
67
+ constructor(engine, reason) {
68
+ super(`Engine '${engine}' is not available${reason ? `: ${reason}` : ""}`, "ENGINE_NOT_AVAILABLE", false);
69
+ this.name = "EngineNotAvailableError";
70
+ }
71
+ };
72
+ NavigationError = class NavigationError extends BrowserError {
73
+ constructor(url, reason) {
74
+ super(`Navigation to '${url}' failed${reason ? `: ${reason}` : ""}`, "NAVIGATION_ERROR", true);
75
+ this.name = "NavigationError";
76
+ }
77
+ };
78
+ ElementNotFoundError = class ElementNotFoundError extends BrowserError {
79
+ constructor(selector) {
80
+ super(`Element not found: ${selector}`, "ELEMENT_NOT_FOUND", false);
81
+ this.name = "ElementNotFoundError";
82
+ }
83
+ };
84
+ RecordingNotFoundError = class RecordingNotFoundError extends BrowserError {
85
+ constructor(id) {
86
+ super(`Recording not found: ${id}`, "RECORDING_NOT_FOUND", false);
87
+ this.name = "RecordingNotFoundError";
88
+ }
89
+ };
90
+ AgentNotFoundError = class AgentNotFoundError extends BrowserError {
91
+ constructor(id) {
92
+ super(`Agent not found: ${id}`, "AGENT_NOT_FOUND", false);
93
+ this.name = "AgentNotFoundError";
94
+ }
95
+ };
96
+ ProjectNotFoundError = class ProjectNotFoundError extends BrowserError {
97
+ constructor(id) {
98
+ super(`Project not found: ${id}`, "PROJECT_NOT_FOUND", false);
99
+ this.name = "ProjectNotFoundError";
100
+ }
101
+ };
102
+ });
103
+
31
104
  // src/db/schema.ts
32
105
  import { Database } from "bun:sqlite";
33
106
  import { join } from "path";
@@ -211,6 +284,55 @@ function runMigrations(db) {
211
284
  CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
212
285
  CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
213
286
  `
287
+ },
288
+ {
289
+ version: 3,
290
+ sql: `
291
+ -- Session lock/claim for multi-agent ownership
292
+ ALTER TABLE sessions ADD COLUMN locked_by TEXT;
293
+ ALTER TABLE sessions ADD COLUMN locked_at TEXT;
294
+ `
295
+ },
296
+ {
297
+ version: 4,
298
+ sql: `
299
+ CREATE TABLE IF NOT EXISTS session_events (
300
+ id TEXT PRIMARY KEY,
301
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
302
+ event_type TEXT NOT NULL,
303
+ details TEXT DEFAULT '{}',
304
+ timestamp TEXT DEFAULT (datetime('now'))
305
+ );
306
+ CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id, timestamp);
307
+ `
308
+ },
309
+ {
310
+ version: 5,
311
+ sql: `
312
+ CREATE TABLE IF NOT EXISTS session_tags (
313
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
314
+ tag TEXT NOT NULL,
315
+ PRIMARY KEY (session_id, tag)
316
+ );
317
+ CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
318
+ `
319
+ },
320
+ {
321
+ version: 6,
322
+ sql: `
323
+ CREATE TABLE IF NOT EXISTS auth_flows (
324
+ id TEXT PRIMARY KEY,
325
+ name TEXT NOT NULL UNIQUE,
326
+ domain TEXT NOT NULL,
327
+ recording_id TEXT REFERENCES recordings(id),
328
+ storage_state_path TEXT,
329
+ created_at TEXT DEFAULT (datetime('now')),
330
+ last_used TEXT
331
+ );
332
+
333
+ CREATE INDEX IF NOT EXISTS idx_auth_flows_domain ON auth_flows(domain);
334
+ CREATE INDEX IF NOT EXISTS idx_auth_flows_name ON auth_flows(name);
335
+ `
214
336
  }
215
337
  ];
216
338
  for (const m of migrations) {
@@ -259,6 +381,188 @@ var init_console_log = __esm(() => {
259
381
  init_schema();
260
382
  });
261
383
 
384
+ // src/engines/cdp.ts
385
+ var exports_cdp = {};
386
+ __export(exports_cdp, {
387
+ connectToExistingBrowser: () => connectToExistingBrowser,
388
+ CDPClient: () => CDPClient
389
+ });
390
+ async function connectToExistingBrowser(cdpUrl) {
391
+ const { chromium: chromium2 } = await import("playwright");
392
+ try {
393
+ return await chromium2.connectOverCDP(cdpUrl);
394
+ } catch (err) {
395
+ throw new BrowserError(`Failed to connect to browser at ${cdpUrl}: ${err instanceof Error ? err.message : String(err)}. Start Chrome with: google-chrome --remote-debugging-port=9222`, "CDP_CONNECT_FAILED", true);
396
+ }
397
+ }
398
+
399
+ class CDPClient {
400
+ session;
401
+ networkEnabled = false;
402
+ performanceEnabled = false;
403
+ constructor(session) {
404
+ this.session = session;
405
+ }
406
+ static async fromPage(page) {
407
+ try {
408
+ const session = await page.context().newCDPSession(page);
409
+ return new CDPClient(session);
410
+ } catch (err) {
411
+ throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
412
+ }
413
+ }
414
+ async send(method, params) {
415
+ try {
416
+ return await this.session.send(method, params);
417
+ } catch (err) {
418
+ throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
419
+ }
420
+ }
421
+ on(event, handler) {
422
+ this.session.on(event, handler);
423
+ }
424
+ off(event, handler) {
425
+ this.session.off(event, handler);
426
+ }
427
+ async enableNetwork() {
428
+ if (!this.networkEnabled) {
429
+ await this.send("Network.enable");
430
+ this.networkEnabled = true;
431
+ }
432
+ }
433
+ async enablePerformance() {
434
+ if (!this.performanceEnabled) {
435
+ await this.send("Performance.enable");
436
+ this.performanceEnabled = true;
437
+ }
438
+ }
439
+ async getPerformanceMetrics() {
440
+ await this.enablePerformance();
441
+ const result = await this.send("Performance.getMetrics");
442
+ const m = {};
443
+ for (const metric of result.metrics) {
444
+ m[metric.name] = metric.value;
445
+ }
446
+ return {
447
+ js_heap_size_used: m["JSHeapUsedSize"],
448
+ js_heap_size_total: m["JSHeapTotalSize"],
449
+ dom_interactive: m["DOMInteractive"],
450
+ dom_complete: m["DOMComplete"],
451
+ load_event: m["LoadEventEnd"]
452
+ };
453
+ }
454
+ async startJSCoverage() {
455
+ await this.send("Profiler.enable");
456
+ await this.send("Debugger.enable");
457
+ await this.send("Profiler.startPreciseCoverage", {
458
+ callCount: false,
459
+ detailed: true
460
+ });
461
+ }
462
+ async stopJSCoverage() {
463
+ const result = await this.send("Profiler.takePreciseCoverage");
464
+ await this.send("Profiler.stopPreciseCoverage");
465
+ return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
466
+ url: r.url,
467
+ text: "",
468
+ ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
469
+ }));
470
+ }
471
+ async getCoverage() {
472
+ await this.startJSCoverage();
473
+ const js = await this.stopJSCoverage();
474
+ const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
475
+ return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
476
+ }
477
+ async captureHAREntries(page, handler) {
478
+ await this.enableNetwork();
479
+ const requestTimings = new Map;
480
+ const onRequest = (params) => {
481
+ requestTimings.set(params.requestId, params.timestamp);
482
+ };
483
+ const onResponse = (params) => {
484
+ const start = requestTimings.get(params.requestId);
485
+ const duration = start != null ? (params.timestamp - start) * 1000 : 0;
486
+ handler({
487
+ method: "GET",
488
+ url: params.response.url,
489
+ status: params.response.status,
490
+ duration
491
+ });
492
+ };
493
+ this.on("Network.requestWillBeSent", onRequest);
494
+ this.on("Network.responseReceived", onResponse);
495
+ return () => {
496
+ this.off("Network.requestWillBeSent", onRequest);
497
+ this.off("Network.responseReceived", onResponse);
498
+ };
499
+ }
500
+ async detach() {
501
+ try {
502
+ await this.session.detach();
503
+ } catch {}
504
+ }
505
+ }
506
+ var init_cdp = __esm(() => {
507
+ init_types();
508
+ });
509
+
510
+ // src/lib/storage-state.ts
511
+ var exports_storage_state = {};
512
+ __export(exports_storage_state, {
513
+ saveStateFromPage: () => saveStateFromPage,
514
+ saveState: () => saveState,
515
+ loadStatePath: () => loadStatePath,
516
+ listStates: () => listStates,
517
+ deleteState: () => deleteState
518
+ });
519
+ import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
520
+ import { join as join3 } from "path";
521
+ import { homedir as homedir3 } from "os";
522
+ function ensureDir() {
523
+ mkdirSync3(STATES_DIR, { recursive: true });
524
+ }
525
+ function statePath(name) {
526
+ return join3(STATES_DIR, `${name}.json`);
527
+ }
528
+ async function saveState(context, name) {
529
+ ensureDir();
530
+ const path = statePath(name);
531
+ const state = await context.storageState({ path });
532
+ return path;
533
+ }
534
+ async function saveStateFromPage(page, name) {
535
+ return saveState(page.context(), name);
536
+ }
537
+ function loadStatePath(name) {
538
+ const path = statePath(name);
539
+ return existsSync(path) ? path : null;
540
+ }
541
+ function listStates() {
542
+ ensureDir();
543
+ return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
544
+ const path = join3(STATES_DIR, f);
545
+ const stat = Bun.file(path);
546
+ return {
547
+ name: f.replace(".json", ""),
548
+ path,
549
+ modified: new Date(stat.lastModified).toISOString()
550
+ };
551
+ }).sort((a, b) => b.modified.localeCompare(a.modified));
552
+ }
553
+ function deleteState(name) {
554
+ const path = statePath(name);
555
+ if (existsSync(path)) {
556
+ unlinkSync(path);
557
+ return true;
558
+ }
559
+ return false;
560
+ }
561
+ var STATES_DIR;
562
+ var init_storage_state = __esm(() => {
563
+ STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
564
+ });
565
+
262
566
  // node_modules/sharp/lib/is.js
263
567
  var require_is = __commonJS((exports, module) => {
264
568
  /*!
@@ -6654,90 +6958,13 @@ var require_lib = __commonJS((exports, module) => {
6654
6958
  module.exports = Sharp;
6655
6959
  });
6656
6960
 
6657
- // src/types/index.ts
6658
- var UseCase;
6659
- ((UseCase2) => {
6660
- UseCase2["SCRAPE"] = "scrape";
6661
- UseCase2["EXTRACT_LINKS"] = "extract_links";
6662
- UseCase2["STATUS_CHECK"] = "status_check";
6663
- UseCase2["FORM_FILL"] = "form_fill";
6664
- UseCase2["SPA_NAVIGATE"] = "spa_navigate";
6665
- UseCase2["SCREENSHOT"] = "screenshot";
6666
- UseCase2["AUTH_FLOW"] = "auth_flow";
6667
- UseCase2["MULTI_TAB"] = "multi_tab";
6668
- UseCase2["NETWORK_MONITOR"] = "network_monitor";
6669
- UseCase2["HAR_CAPTURE"] = "har_capture";
6670
- UseCase2["PERF_PROFILE"] = "perf_profile";
6671
- UseCase2["SCRIPT_INJECT"] = "script_inject";
6672
- UseCase2["COVERAGE"] = "coverage";
6673
- UseCase2["RECORD_REPLAY"] = "record_replay";
6674
- })(UseCase ||= {});
6675
-
6676
- class BrowserError extends Error {
6677
- code;
6678
- retryable;
6679
- constructor(message, code = "BROWSER_ERROR", retryable = false) {
6680
- super(message);
6681
- this.code = code;
6682
- this.retryable = retryable;
6683
- this.name = "BrowserError";
6684
- }
6685
- }
6686
-
6687
- class SessionNotFoundError extends BrowserError {
6688
- constructor(id) {
6689
- super(`Session not found: ${id}`, "SESSION_NOT_FOUND", false);
6690
- this.name = "SessionNotFoundError";
6691
- }
6692
- }
6693
-
6694
- class EngineNotAvailableError extends BrowserError {
6695
- constructor(engine, reason) {
6696
- super(`Engine '${engine}' is not available${reason ? `: ${reason}` : ""}`, "ENGINE_NOT_AVAILABLE", false);
6697
- this.name = "EngineNotAvailableError";
6698
- }
6699
- }
6700
-
6701
- class NavigationError extends BrowserError {
6702
- constructor(url, reason) {
6703
- super(`Navigation to '${url}' failed${reason ? `: ${reason}` : ""}`, "NAVIGATION_ERROR", true);
6704
- this.name = "NavigationError";
6705
- }
6706
- }
6707
-
6708
- class ElementNotFoundError extends BrowserError {
6709
- constructor(selector) {
6710
- super(`Element not found: ${selector}`, "ELEMENT_NOT_FOUND", false);
6711
- this.name = "ElementNotFoundError";
6712
- }
6713
- }
6714
-
6715
- class RecordingNotFoundError extends BrowserError {
6716
- constructor(id) {
6717
- super(`Recording not found: ${id}`, "RECORDING_NOT_FOUND", false);
6718
- this.name = "RecordingNotFoundError";
6719
- }
6720
- }
6721
-
6722
- class AgentNotFoundError extends BrowserError {
6723
- constructor(id) {
6724
- super(`Agent not found: ${id}`, "AGENT_NOT_FOUND", false);
6725
- this.name = "AgentNotFoundError";
6726
- }
6727
- }
6728
-
6729
- class ProjectNotFoundError extends BrowserError {
6730
- constructor(id) {
6731
- super(`Project not found: ${id}`, "PROJECT_NOT_FOUND", false);
6732
- this.name = "ProjectNotFoundError";
6733
- }
6734
- }
6735
-
6736
6961
  // src/index.ts
6737
6962
  init_schema();
6963
+ init_types();
6738
6964
 
6739
6965
  // src/db/projects.ts
6740
6966
  init_schema();
6967
+ init_types();
6741
6968
  import { randomUUID } from "crypto";
6742
6969
  function createProject(data) {
6743
6970
  const db = getDatabase();
@@ -6795,6 +7022,7 @@ function deleteProject(id) {
6795
7022
  }
6796
7023
  // src/db/agents.ts
6797
7024
  init_schema();
7025
+ init_types();
6798
7026
  import { randomUUID as randomUUID2 } from "crypto";
6799
7027
  function registerAgent(name, opts = {}) {
6800
7028
  const db = getDatabase();
@@ -6875,6 +7103,7 @@ function cleanStaleAgents(thresholdMs) {
6875
7103
  }
6876
7104
  // src/db/sessions.ts
6877
7105
  init_schema();
7106
+ init_types();
6878
7107
  import { randomUUID as randomUUID3 } from "crypto";
6879
7108
  function createSession(data) {
6880
7109
  const db = getDatabase();
@@ -6927,8 +7156,24 @@ function updateSessionStatus(id, status) {
6927
7156
  return getSession(id);
6928
7157
  }
6929
7158
  function closeSession(id) {
7159
+ const db = getDatabase();
7160
+ db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
6930
7161
  return updateSessionStatus(id, "closed");
6931
7162
  }
7163
+ function getActiveSessionForAgent(agentId) {
7164
+ const db = getDatabase();
7165
+ return db.query("SELECT * FROM sessions WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1").get(agentId) ?? null;
7166
+ }
7167
+ function getDefaultActiveSession() {
7168
+ const db = getDatabase();
7169
+ const rows = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY created_at DESC LIMIT 2").all();
7170
+ return rows.length === 1 ? rows[0] : null;
7171
+ }
7172
+ function countActiveSessions() {
7173
+ const db = getDatabase();
7174
+ const row = db.query("SELECT COUNT(*) as count FROM sessions WHERE status = 'active'").get();
7175
+ return row?.count ?? 0;
7176
+ }
6932
7177
  function deleteSession(id) {
6933
7178
  const db = getDatabase();
6934
7179
  db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
@@ -6991,6 +7236,7 @@ init_console_log();
6991
7236
 
6992
7237
  // src/db/recordings.ts
6993
7238
  init_schema();
7239
+ init_types();
6994
7240
  import { randomUUID as randomUUID7 } from "crypto";
6995
7241
  function deserialize(row) {
6996
7242
  return {
@@ -7105,6 +7351,7 @@ function cleanOldHeartbeats(olderThanMs) {
7105
7351
  return result.changes;
7106
7352
  }
7107
7353
  // src/engines/playwright.ts
7354
+ init_types();
7108
7355
  import { chromium } from "playwright";
7109
7356
  var DEFAULT_VIEWPORT = { width: 1280, height: 720 };
7110
7357
  async function launchPlaywright(options) {
@@ -7144,14 +7391,14 @@ class BrowserPool {
7144
7391
  this.maxSize = maxSize;
7145
7392
  this.options = options;
7146
7393
  }
7147
- async acquire() {
7394
+ async acquire(headless = true) {
7148
7395
  const available = this.pool.find((e) => !e.inUse);
7149
7396
  if (available) {
7150
7397
  available.inUse = true;
7151
7398
  return available.browser;
7152
7399
  }
7153
7400
  if (this.pool.length < this.maxSize) {
7154
- const browser = await launchPlaywright(this.options);
7401
+ const browser = await launchPlaywright({ ...this.options, headless });
7155
7402
  this.pool.push({ browser, inUse: true, createdAt: Date.now() });
7156
7403
  return browser;
7157
7404
  }
@@ -7182,115 +7429,12 @@ class BrowserPool {
7182
7429
  return this.pool.filter((e) => !e.inUse).length;
7183
7430
  }
7184
7431
  }
7185
- // src/engines/cdp.ts
7186
- class CDPClient {
7187
- session;
7188
- networkEnabled = false;
7189
- performanceEnabled = false;
7190
- constructor(session) {
7191
- this.session = session;
7192
- }
7193
- static async fromPage(page) {
7194
- try {
7195
- const session = await page.context().newCDPSession(page);
7196
- return new CDPClient(session);
7197
- } catch (err) {
7198
- throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
7199
- }
7200
- }
7201
- async send(method, params) {
7202
- try {
7203
- return await this.session.send(method, params);
7204
- } catch (err) {
7205
- throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
7206
- }
7207
- }
7208
- on(event, handler) {
7209
- this.session.on(event, handler);
7210
- }
7211
- off(event, handler) {
7212
- this.session.off(event, handler);
7213
- }
7214
- async enableNetwork() {
7215
- if (!this.networkEnabled) {
7216
- await this.send("Network.enable");
7217
- this.networkEnabled = true;
7218
- }
7219
- }
7220
- async enablePerformance() {
7221
- if (!this.performanceEnabled) {
7222
- await this.send("Performance.enable");
7223
- this.performanceEnabled = true;
7224
- }
7225
- }
7226
- async getPerformanceMetrics() {
7227
- await this.enablePerformance();
7228
- const result = await this.send("Performance.getMetrics");
7229
- const m = {};
7230
- for (const metric of result.metrics) {
7231
- m[metric.name] = metric.value;
7232
- }
7233
- return {
7234
- js_heap_size_used: m["JSHeapUsedSize"],
7235
- js_heap_size_total: m["JSHeapTotalSize"],
7236
- dom_interactive: m["DOMInteractive"],
7237
- dom_complete: m["DOMComplete"],
7238
- load_event: m["LoadEventEnd"]
7239
- };
7240
- }
7241
- async startJSCoverage() {
7242
- await this.send("Profiler.enable");
7243
- await this.send("Debugger.enable");
7244
- await this.send("Profiler.startPreciseCoverage", {
7245
- callCount: false,
7246
- detailed: true
7247
- });
7248
- }
7249
- async stopJSCoverage() {
7250
- const result = await this.send("Profiler.takePreciseCoverage");
7251
- await this.send("Profiler.stopPreciseCoverage");
7252
- return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
7253
- url: r.url,
7254
- text: "",
7255
- ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
7256
- }));
7257
- }
7258
- async getCoverage() {
7259
- await this.startJSCoverage();
7260
- const js = await this.stopJSCoverage();
7261
- const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
7262
- return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
7263
- }
7264
- async captureHAREntries(page, handler) {
7265
- await this.enableNetwork();
7266
- const requestTimings = new Map;
7267
- const onRequest = (params) => {
7268
- requestTimings.set(params.requestId, params.timestamp);
7269
- };
7270
- const onResponse = (params) => {
7271
- const start = requestTimings.get(params.requestId);
7272
- const duration = start != null ? (params.timestamp - start) * 1000 : 0;
7273
- handler({
7274
- method: "GET",
7275
- url: params.response.url,
7276
- status: params.response.status,
7277
- duration
7278
- });
7279
- };
7280
- this.on("Network.requestWillBeSent", onRequest);
7281
- this.on("Network.responseReceived", onResponse);
7282
- return () => {
7283
- this.off("Network.requestWillBeSent", onRequest);
7284
- this.off("Network.responseReceived", onResponse);
7285
- };
7286
- }
7287
- async detach() {
7288
- try {
7289
- await this.session.detach();
7290
- } catch {}
7291
- }
7292
- }
7432
+
7433
+ // src/index.ts
7434
+ init_cdp();
7435
+
7293
7436
  // src/engines/lightpanda.ts
7437
+ init_types();
7294
7438
  import { execSync, spawn } from "child_process";
7295
7439
  import { chromium as chromium2 } from "playwright";
7296
7440
  var DEFAULT_LIGHTPANDA_PORT = 9222;
@@ -7432,6 +7576,9 @@ class LightpandaPage {
7432
7576
  await this.page.context().close();
7433
7577
  }
7434
7578
  }
7579
+ // src/engines/selector.ts
7580
+ init_types();
7581
+
7435
7582
  // src/engines/bun-webview.ts
7436
7583
  import { join as join2 } from "path";
7437
7584
  import { mkdirSync as mkdirSync2 } from "fs";
@@ -7905,6 +8052,10 @@ function inferUseCase(label) {
7905
8052
  };
7906
8053
  return map[label.toLowerCase()] ?? "spa_navigate" /* SPA_NAVIGATE */;
7907
8054
  }
8055
+ // src/lib/session.ts
8056
+ init_types();
8057
+ init_types();
8058
+
7908
8059
  // src/lib/network.ts
7909
8060
  function enableNetworkLogging(page, sessionId) {
7910
8061
  const requestStart = new Map;
@@ -8163,10 +8314,55 @@ function setupDialogHandler(page, sessionId) {
8163
8314
 
8164
8315
  // src/lib/session.ts
8165
8316
  var handles = new Map;
8317
+ var pool = new BrowserPool(5);
8318
+ var SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
8319
+ var ttlInterval = setInterval(async () => {
8320
+ const now = Date.now();
8321
+ for (const [id, handle] of handles) {
8322
+ if (now - handle.lastActivity > SESSION_TTL_MS) {
8323
+ try {
8324
+ await closeSession2(id);
8325
+ } catch {}
8326
+ }
8327
+ }
8328
+ }, 60000);
8329
+ if (ttlInterval.unref)
8330
+ ttlInterval.unref();
8166
8331
  function createBunProxy(view) {
8167
8332
  return view;
8168
8333
  }
8169
8334
  async function createSession2(opts = {}) {
8335
+ if (opts.cdpUrl) {
8336
+ const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
8337
+ const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
8338
+ const contexts = cdpBrowser.contexts();
8339
+ const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
8340
+ const pages = context.pages();
8341
+ const page2 = pages.length > 0 ? pages[0] : await context.newPage();
8342
+ const session2 = createSession({
8343
+ engine: "cdp",
8344
+ projectId: opts.projectId,
8345
+ agentId: opts.agentId,
8346
+ startUrl: page2.url(),
8347
+ name: opts.name ?? "attached"
8348
+ });
8349
+ const cleanups2 = [];
8350
+ if (opts.captureNetwork !== false) {
8351
+ try {
8352
+ cleanups2.push(enableNetworkLogging(page2, session2.id));
8353
+ } catch {}
8354
+ }
8355
+ if (opts.captureConsole !== false) {
8356
+ try {
8357
+ cleanups2.push(enableConsoleCapture(page2, session2.id));
8358
+ } catch {}
8359
+ }
8360
+ try {
8361
+ cleanups2.push(setupDialogHandler(page2, session2.id));
8362
+ } catch {}
8363
+ 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 });
8364
+ return { session: session2, page: page2 };
8365
+ }
8170
8366
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
8171
8367
  const resolvedEngine = engine === "auto" ? "playwright" : engine;
8172
8368
  let browser = null;
@@ -8191,12 +8387,23 @@ async function createSession2(opts = {}) {
8191
8387
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
8192
8388
  page = await context.newPage();
8193
8389
  } else {
8194
- browser = await launchPlaywright({
8195
- headless: opts.headless ?? true,
8196
- viewport: opts.viewport,
8197
- userAgent: opts.userAgent
8198
- });
8199
- page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8390
+ browser = await pool.acquire(opts.headless ?? true);
8391
+ if (opts.storageState) {
8392
+ const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
8393
+ const statePath2 = loadStatePath2(opts.storageState);
8394
+ if (statePath2) {
8395
+ const context = await browser.newContext({
8396
+ viewport: opts.viewport ?? { width: 1280, height: 720 },
8397
+ userAgent: opts.userAgent,
8398
+ storageState: statePath2
8399
+ });
8400
+ page = await context.newPage();
8401
+ } else {
8402
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8403
+ }
8404
+ } else {
8405
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8406
+ }
8200
8407
  }
8201
8408
  const sessionName = opts.name ?? (opts.startUrl ? (() => {
8202
8409
  try {
@@ -8249,7 +8456,7 @@ async function createSession2(opts = {}) {
8249
8456
  } catch {}
8250
8457
  }
8251
8458
  }
8252
- handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
8459
+ handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
8253
8460
  if (opts.startUrl) {
8254
8461
  try {
8255
8462
  if (bunView) {
@@ -8275,6 +8482,7 @@ function getSessionPage(sessionId) {
8275
8482
  handles.delete(sessionId);
8276
8483
  throw new SessionNotFoundError(sessionId);
8277
8484
  }
8485
+ handle.lastActivity = Date.now();
8278
8486
  return handle.page;
8279
8487
  }
8280
8488
  function getSessionBunView(sessionId) {
@@ -8322,10 +8530,8 @@ async function closeSession2(sessionId) {
8322
8530
  try {
8323
8531
  await handle.page.context().close();
8324
8532
  } catch {}
8325
- try {
8326
- if (handle.browser)
8327
- await closeBrowser(handle.browser);
8328
- } catch {}
8533
+ if (handle.browser)
8534
+ pool.release(handle.browser);
8329
8535
  }
8330
8536
  handles.delete(sessionId);
8331
8537
  }
@@ -8341,6 +8547,7 @@ async function closeAllSessions() {
8341
8547
  for (const [id] of handles) {
8342
8548
  await closeSession2(id).catch(() => {});
8343
8549
  }
8550
+ await pool.destroyAll();
8344
8551
  }
8345
8552
  function getSessionByName2(name) {
8346
8553
  return getSessionByName(name);
@@ -8352,6 +8559,51 @@ function getTokenBudget(sessionId) {
8352
8559
  const handle = handles.get(sessionId);
8353
8560
  return handle ? handle.tokenBudget : null;
8354
8561
  }
8562
+ function getActiveSessionForAgent2(agentId) {
8563
+ const session = getActiveSessionForAgent(agentId);
8564
+ if (!session)
8565
+ return null;
8566
+ const handle = handles.get(session.id);
8567
+ if (!handle)
8568
+ return null;
8569
+ try {
8570
+ if (handle.bunView)
8571
+ handle.bunView.url();
8572
+ else
8573
+ handle.page.url();
8574
+ } catch {
8575
+ handles.delete(session.id);
8576
+ return null;
8577
+ }
8578
+ return { session, page: handle.page };
8579
+ }
8580
+ function getDefaultSession() {
8581
+ const session = getDefaultActiveSession();
8582
+ if (!session)
8583
+ return null;
8584
+ const handle = handles.get(session.id);
8585
+ if (!handle)
8586
+ return null;
8587
+ try {
8588
+ if (handle.bunView)
8589
+ handle.bunView.url();
8590
+ else
8591
+ handle.page.url();
8592
+ } catch {
8593
+ handles.delete(session.id);
8594
+ return null;
8595
+ }
8596
+ return { session, page: handle.page };
8597
+ }
8598
+ function isAutoGallery(sessionId) {
8599
+ return handles.get(sessionId)?.autoGallery ?? false;
8600
+ }
8601
+ function countActiveSessions2() {
8602
+ return countActiveSessions();
8603
+ }
8604
+ // src/lib/actions.ts
8605
+ init_types();
8606
+
8355
8607
  // src/lib/snapshot.ts
8356
8608
  var lastSnapshots = new Map;
8357
8609
  var sessionRefMaps = new Map;
@@ -8365,6 +8617,66 @@ function getRefLocator(page, sessionId, ref) {
8365
8617
  return page.getByRole(entry.role, { name: entry.name }).first();
8366
8618
  }
8367
8619
 
8620
+ // src/lib/self-heal.ts
8621
+ async function healSelector(page, selector, sessionId) {
8622
+ const attempts = [];
8623
+ attempts.push(`selector: ${selector}`);
8624
+ try {
8625
+ const loc = page.locator(selector).first();
8626
+ if (await loc.count() > 0) {
8627
+ return { found: true, locator: loc, method: "original", healed: false, attempts };
8628
+ }
8629
+ } catch {}
8630
+ if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[") && !selector.includes(">") && !selector.includes(" ")) {
8631
+ attempts.push(`text: "${selector}"`);
8632
+ try {
8633
+ const loc = page.getByText(selector, { exact: false }).first();
8634
+ if (await loc.count() > 0) {
8635
+ return { found: true, locator: loc, method: "text", healed: true, attempts };
8636
+ }
8637
+ } catch {}
8638
+ }
8639
+ const roleMap = {
8640
+ button: ["button", "submit", "reset"],
8641
+ link: ["a"],
8642
+ input: ["input", "textarea"],
8643
+ heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
8644
+ };
8645
+ const nameHint = selector.replace(/^[#.]/, "").replace(/[-_]/g, " ").toLowerCase();
8646
+ for (const [role, tags] of Object.entries(roleMap)) {
8647
+ attempts.push(`role: ${role} name~="${nameHint}"`);
8648
+ try {
8649
+ const loc = page.getByRole(role, { name: new RegExp(nameHint.split(" ")[0], "i") }).first();
8650
+ if (await loc.count() > 0) {
8651
+ return { found: true, locator: loc, method: "role", healed: true, attempts };
8652
+ }
8653
+ } catch {}
8654
+ }
8655
+ if (selector.startsWith("#")) {
8656
+ const idPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
8657
+ const partialSel = `[id*="${idPart}"]`;
8658
+ attempts.push(`partial_id: ${partialSel}`);
8659
+ try {
8660
+ const loc = page.locator(partialSel).first();
8661
+ if (await loc.count() > 0) {
8662
+ return { found: true, locator: loc, method: "partial_id", healed: true, attempts };
8663
+ }
8664
+ } catch {}
8665
+ }
8666
+ if (selector.startsWith(".")) {
8667
+ const classPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
8668
+ const partialSel = `[class*="${classPart}"]`;
8669
+ attempts.push(`partial_class: ${partialSel}`);
8670
+ try {
8671
+ const loc = page.locator(partialSel).first();
8672
+ if (await loc.count() > 0) {
8673
+ return { found: true, locator: loc, method: "partial_class", healed: true, attempts };
8674
+ }
8675
+ } catch {}
8676
+ }
8677
+ return { found: false, locator: null, method: "none", healed: false, attempts };
8678
+ }
8679
+
8368
8680
  // src/lib/actions.ts
8369
8681
  async function click(page, selector, opts) {
8370
8682
  try {
@@ -8374,11 +8686,22 @@ async function click(page, selector, opts) {
8374
8686
  delay: opts?.delay,
8375
8687
  timeout: opts?.timeout ?? 1e4
8376
8688
  });
8377
- } catch (err) {
8378
- if (err instanceof Error && err.message.includes("not found")) {
8689
+ return {};
8690
+ } catch (originalError) {
8691
+ if (opts?.selfHeal !== false) {
8692
+ const result = await healSelector(page, selector);
8693
+ if (result.found && result.locator) {
8694
+ await result.locator.click({
8695
+ button: opts?.button ?? "left",
8696
+ timeout: opts?.timeout ?? 1e4
8697
+ });
8698
+ return { healed: true, method: result.method, attempts: result.attempts };
8699
+ }
8700
+ }
8701
+ if (originalError instanceof Error && originalError.message.includes("not found")) {
8379
8702
  throw new ElementNotFoundError(selector);
8380
8703
  }
8381
- throw new BrowserError(`Click failed on '${selector}': ${err instanceof Error ? err.message : String(err)}`, "CLICK_FAILED");
8704
+ throw new BrowserError(`Click failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "CLICK_FAILED");
8382
8705
  }
8383
8706
  }
8384
8707
  async function type(page, selector, text, opts) {
@@ -8387,17 +8710,35 @@ async function type(page, selector, text, opts) {
8387
8710
  await page.fill(selector, "", { timeout: opts?.timeout ?? 1e4 });
8388
8711
  }
8389
8712
  await page.type(selector, text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
8390
- } catch (err) {
8391
- if (err instanceof Error && err.message.includes("not found")) {
8713
+ return {};
8714
+ } catch (originalError) {
8715
+ if (opts?.selfHeal !== false) {
8716
+ const result = await healSelector(page, selector);
8717
+ if (result.found && result.locator) {
8718
+ if (opts?.clear)
8719
+ await result.locator.fill("", { timeout: opts?.timeout ?? 1e4 });
8720
+ await result.locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
8721
+ return { healed: true, method: result.method, attempts: result.attempts };
8722
+ }
8723
+ }
8724
+ if (originalError instanceof Error && originalError.message.includes("not found")) {
8392
8725
  throw new ElementNotFoundError(selector);
8393
8726
  }
8394
- throw new BrowserError(`Type failed on '${selector}': ${err instanceof Error ? err.message : String(err)}`, "TYPE_FAILED");
8727
+ throw new BrowserError(`Type failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "TYPE_FAILED");
8395
8728
  }
8396
8729
  }
8397
- async function fill(page, selector, value, timeout = 1e4) {
8730
+ async function fill(page, selector, value, timeout = 1e4, selfHeal = true) {
8398
8731
  try {
8399
8732
  await page.fill(selector, value, { timeout });
8400
- } catch (err) {
8733
+ return {};
8734
+ } catch (originalError) {
8735
+ if (selfHeal) {
8736
+ const result = await healSelector(page, selector);
8737
+ if (result.found && result.locator) {
8738
+ await result.locator.fill(value, { timeout });
8739
+ return { healed: true, method: result.method, attempts: result.attempts };
8740
+ }
8741
+ }
8401
8742
  throw new ElementNotFoundError(selector);
8402
8743
  }
8403
8744
  }
@@ -8523,12 +8864,39 @@ async function clickText(page, text, opts) {
8523
8864
  }
8524
8865
  }, { retries: opts?.retries ?? 1 });
8525
8866
  }
8526
- async function fillForm(page, fields, submitSelector) {
8867
+ async function fillForm(page, fields, submitSelector, selfHeal = true) {
8527
8868
  let filled = 0;
8528
8869
  const errors = [];
8870
+ const healedFields = [];
8529
8871
  for (const [selector, value] of Object.entries(fields)) {
8530
8872
  try {
8531
- const el = await page.$(selector);
8873
+ let el = await page.$(selector);
8874
+ if (!el && selfHeal) {
8875
+ const result = await healSelector(page, selector);
8876
+ if (result.found && result.locator) {
8877
+ const handle = await result.locator.elementHandle();
8878
+ if (handle) {
8879
+ el = handle;
8880
+ healedFields.push(selector);
8881
+ const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
8882
+ const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
8883
+ if (tagName2 === "select") {
8884
+ await result.locator.selectOption(String(value));
8885
+ } else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
8886
+ if (Boolean(value))
8887
+ await result.locator.check();
8888
+ else
8889
+ await result.locator.uncheck();
8890
+ } else {
8891
+ await result.locator.fill(String(value));
8892
+ }
8893
+ filled++;
8894
+ continue;
8895
+ }
8896
+ }
8897
+ errors.push(`${selector}: element not found`);
8898
+ continue;
8899
+ }
8532
8900
  if (!el) {
8533
8901
  errors.push(`${selector}: element not found`);
8534
8902
  continue;
@@ -8555,11 +8923,21 @@ async function fillForm(page, fields, submitSelector) {
8555
8923
  if (submitSelector) {
8556
8924
  try {
8557
8925
  await page.click(submitSelector);
8558
- } catch (err) {
8559
- errors.push(`submit(${submitSelector}): ${err instanceof Error ? err.message : String(err)}`);
8926
+ } catch (submitErr) {
8927
+ if (selfHeal) {
8928
+ const result = await healSelector(page, submitSelector);
8929
+ if (result.found && result.locator) {
8930
+ await result.locator.click();
8931
+ healedFields.push(submitSelector);
8932
+ } else {
8933
+ errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
8934
+ }
8935
+ } else {
8936
+ errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
8937
+ }
8560
8938
  }
8561
8939
  }
8562
- return { filled, errors, fields_attempted: Object.keys(fields).length };
8940
+ return { filled, errors, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
8563
8941
  }
8564
8942
  async function waitForText(page, text, opts) {
8565
8943
  const timeout = opts?.timeout ?? 1e4;
@@ -8839,6 +9217,7 @@ async function getPageInfo(page) {
8839
9217
  };
8840
9218
  }
8841
9219
  // src/lib/performance.ts
9220
+ init_cdp();
8842
9221
  async function getPerformanceMetrics(page) {
8843
9222
  const navTiming = await page.evaluate(() => {
8844
9223
  const t = performance.timing;
@@ -8926,10 +9305,11 @@ async function startCoverage(page) {
8926
9305
  };
8927
9306
  }
8928
9307
  // src/lib/screenshot.ts
9308
+ init_types();
8929
9309
  var import_sharp = __toESM(require_lib(), 1);
8930
- import { join as join3 } from "path";
8931
- import { mkdirSync as mkdirSync3 } from "fs";
8932
- import { homedir as homedir3 } from "os";
9310
+ import { join as join4 } from "path";
9311
+ import { mkdirSync as mkdirSync4 } from "fs";
9312
+ import { homedir as homedir4 } from "os";
8933
9313
 
8934
9314
  // src/db/gallery.ts
8935
9315
  init_schema();
@@ -8975,13 +9355,13 @@ function getEntry(id) {
8975
9355
 
8976
9356
  // src/lib/screenshot.ts
8977
9357
  function getDataDir2() {
8978
- return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
9358
+ return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
8979
9359
  }
8980
9360
  function getScreenshotDir(projectId) {
8981
- const base = join3(getDataDir2(), "screenshots");
9361
+ const base = join4(getDataDir2(), "screenshots");
8982
9362
  const date = new Date().toISOString().split("T")[0];
8983
- const dir = projectId ? join3(base, projectId, date) : join3(base, date);
8984
- mkdirSync3(dir, { recursive: true });
9363
+ const dir = projectId ? join4(base, projectId, date) : join4(base, date);
9364
+ mkdirSync4(dir, { recursive: true });
8985
9365
  return dir;
8986
9366
  }
8987
9367
  async function compressBuffer(raw, format, quality, maxWidth) {
@@ -8996,7 +9376,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
8996
9376
  }
8997
9377
  }
8998
9378
  async function generateThumbnail(raw, dir, stem) {
8999
- const thumbPath = join3(dir, `${stem}.thumb.webp`);
9379
+ const thumbPath = join4(dir, `${stem}.thumb.webp`);
9000
9380
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
9001
9381
  await Bun.write(thumbPath, thumbBuffer);
9002
9382
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -9053,7 +9433,7 @@ async function takeScreenshot(page, opts) {
9053
9433
  const compressedSizeBytes = finalBuffer.length;
9054
9434
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
9055
9435
  const ext = format;
9056
- const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
9436
+ const screenshotPath = opts?.path ?? join4(dir, `${stem}.${ext}`);
9057
9437
  await Bun.write(screenshotPath, finalBuffer);
9058
9438
  let thumbnailPath;
9059
9439
  let thumbnailBase64;
@@ -9113,12 +9493,12 @@ async function takeScreenshot(page, opts) {
9113
9493
  }
9114
9494
  async function generatePDF(page, opts) {
9115
9495
  try {
9116
- const base = join3(getDataDir2(), "pdfs");
9496
+ const base = join4(getDataDir2(), "pdfs");
9117
9497
  const date = new Date().toISOString().split("T")[0];
9118
- const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
9119
- mkdirSync3(dir, { recursive: true });
9498
+ const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
9499
+ mkdirSync4(dir, { recursive: true });
9120
9500
  const timestamp = Date.now();
9121
- const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
9501
+ const pdfPath = opts?.path ?? join4(dir, `${timestamp}.pdf`);
9122
9502
  const buffer = await page.pdf({
9123
9503
  path: pdfPath,
9124
9504
  format: opts?.format ?? "A4",
@@ -9208,6 +9588,7 @@ async function getIndexedDB(page, dbName, storeName) {
9208
9588
  }), [dbName, storeName]);
9209
9589
  }
9210
9590
  // src/lib/recorder.ts
9591
+ init_types();
9211
9592
  var activeRecordings = new Map;
9212
9593
  function startRecording(sessionId, name, startUrl) {
9213
9594
  const steps = [];
@@ -9366,6 +9747,7 @@ function exportRecording(recordingId, format = "json") {
9366
9747
  `);
9367
9748
  }
9368
9749
  // src/lib/crawler.ts
9750
+ init_types();
9369
9751
  async function crawl(startUrl, opts = {}) {
9370
9752
  const maxDepth = opts.maxDepth ?? 2;
9371
9753
  const maxPages = opts.maxPages ?? 50;
@@ -9491,6 +9873,7 @@ export {
9491
9873
  isLightpandaAvailable,
9492
9874
  isEngineAvailable,
9493
9875
  isBunSession,
9876
+ isAutoGallery,
9494
9877
  isAgentStale,
9495
9878
  inferUseCase,
9496
9879
  hoverRef,
@@ -9529,6 +9912,7 @@ export {
9529
9912
  getLastHeartbeat,
9530
9913
  getIndexedDB,
9531
9914
  getHTML,
9915
+ getDefaultSession,
9532
9916
  getDatabase,
9533
9917
  getDataDir,
9534
9918
  getCrawlResult,
@@ -9539,6 +9923,7 @@ export {
9539
9923
  getAgentByName,
9540
9924
  getAgent,
9541
9925
  getActiveSessions,
9926
+ getActiveSessionForAgent2 as getActiveSessionForAgent,
9542
9927
  getActiveAgents,
9543
9928
  generatePDF,
9544
9929
  findElements,
@@ -9574,6 +9959,8 @@ export {
9574
9959
  createProject,
9575
9960
  createCrawlResult,
9576
9961
  crawl,
9962
+ countActiveSessions2 as countActiveSessions,
9963
+ connectToExistingBrowser,
9577
9964
  connectLightpanda,
9578
9965
  closeSession2 as closeSession,
9579
9966
  closePage,
@@ -9593,6 +9980,7 @@ export {
9593
9980
  checkRef,
9594
9981
  checkBox,
9595
9982
  capturePageErrors,
9983
+ pool as browserPool,
9596
9984
  attachPageListeners,
9597
9985
  addInterceptRule,
9598
9986
  UseCase,