@hasna/browser 0.0.6 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2309,6 +2309,38 @@ function runMigrations(db) {
2309
2309
  CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
2310
2310
  CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
2311
2311
  `
2312
+ },
2313
+ {
2314
+ version: 3,
2315
+ sql: `
2316
+ -- Session lock/claim for multi-agent ownership
2317
+ ALTER TABLE sessions ADD COLUMN locked_by TEXT;
2318
+ ALTER TABLE sessions ADD COLUMN locked_at TEXT;
2319
+ `
2320
+ },
2321
+ {
2322
+ version: 4,
2323
+ sql: `
2324
+ CREATE TABLE IF NOT EXISTS session_events (
2325
+ id TEXT PRIMARY KEY,
2326
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
2327
+ event_type TEXT NOT NULL,
2328
+ details TEXT DEFAULT '{}',
2329
+ timestamp TEXT DEFAULT (datetime('now'))
2330
+ );
2331
+ CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id, timestamp);
2332
+ `
2333
+ },
2334
+ {
2335
+ version: 5,
2336
+ sql: `
2337
+ CREATE TABLE IF NOT EXISTS session_tags (
2338
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
2339
+ tag TEXT NOT NULL,
2340
+ PRIMARY KEY (session_id, tag)
2341
+ );
2342
+ CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
2343
+ `
2312
2344
  }
2313
2345
  ];
2314
2346
  for (const m of migrations) {
@@ -2324,6 +2356,28 @@ var _db = null, _dbPath = null;
2324
2356
  var init_schema = () => {};
2325
2357
 
2326
2358
  // src/db/sessions.ts
2359
+ var exports_sessions = {};
2360
+ __export(exports_sessions, {
2361
+ updateSessionStatus: () => updateSessionStatus,
2362
+ unlockSession: () => unlockSession,
2363
+ transferSession: () => transferSession,
2364
+ renameSession: () => renameSession,
2365
+ removeSessionTag: () => removeSessionTag,
2366
+ lockSession: () => lockSession,
2367
+ listSessionsByTag: () => listSessionsByTag,
2368
+ listSessions: () => listSessions,
2369
+ isSessionLocked: () => isSessionLocked,
2370
+ getSessionTags: () => getSessionTags,
2371
+ getSessionByName: () => getSessionByName,
2372
+ getSession: () => getSession,
2373
+ getDefaultActiveSession: () => getDefaultActiveSession,
2374
+ getActiveSessionForAgent: () => getActiveSessionForAgent,
2375
+ deleteSession: () => deleteSession,
2376
+ createSession: () => createSession,
2377
+ countActiveSessions: () => countActiveSessions,
2378
+ closeSession: () => closeSession,
2379
+ addSessionTag: () => addSessionTag
2380
+ });
2327
2381
  import { randomUUID } from "crypto";
2328
2382
  function createSession(data) {
2329
2383
  const db = getDatabase();
@@ -2376,8 +2430,81 @@ function updateSessionStatus(id, status) {
2376
2430
  return getSession(id);
2377
2431
  }
2378
2432
  function closeSession(id) {
2433
+ const db = getDatabase();
2434
+ db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
2379
2435
  return updateSessionStatus(id, "closed");
2380
2436
  }
2437
+ function lockSession(id, agentId) {
2438
+ const db = getDatabase();
2439
+ const session = getSession(id);
2440
+ if (session.status !== "active")
2441
+ throw new SessionNotFoundError(id);
2442
+ const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
2443
+ if (row?.locked_by && row.locked_by !== agentId) {
2444
+ throw new Error(`Session locked by agent ${row.locked_by}`);
2445
+ }
2446
+ db.prepare("UPDATE sessions SET locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(agentId, id);
2447
+ return getSession(id);
2448
+ }
2449
+ function unlockSession(id, agentId) {
2450
+ const db = getDatabase();
2451
+ if (agentId) {
2452
+ const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
2453
+ if (row?.locked_by && row.locked_by !== agentId) {
2454
+ throw new Error(`Session locked by agent ${row.locked_by}, not ${agentId}`);
2455
+ }
2456
+ }
2457
+ db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
2458
+ return getSession(id);
2459
+ }
2460
+ function isSessionLocked(id) {
2461
+ const db = getDatabase();
2462
+ const row = db.query("SELECT locked_by, locked_at FROM sessions WHERE id = ?").get(id);
2463
+ if (!row)
2464
+ throw new SessionNotFoundError(id);
2465
+ return { locked: !!row.locked_by, locked_by: row.locked_by ?? undefined, locked_at: row.locked_at ?? undefined };
2466
+ }
2467
+ function transferSession(id, toAgentId) {
2468
+ const db = getDatabase();
2469
+ db.prepare("UPDATE sessions SET agent_id = ?, locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(toAgentId, toAgentId, id);
2470
+ return getSession(id);
2471
+ }
2472
+ function getActiveSessionForAgent(agentId) {
2473
+ const db = getDatabase();
2474
+ return db.query("SELECT * FROM sessions WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1").get(agentId) ?? null;
2475
+ }
2476
+ function getDefaultActiveSession() {
2477
+ const db = getDatabase();
2478
+ const rows = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY created_at DESC LIMIT 2").all();
2479
+ return rows.length === 1 ? rows[0] : null;
2480
+ }
2481
+ function countActiveSessions() {
2482
+ const db = getDatabase();
2483
+ const row = db.query("SELECT COUNT(*) as count FROM sessions WHERE status = 'active'").get();
2484
+ return row?.count ?? 0;
2485
+ }
2486
+ function deleteSession(id) {
2487
+ const db = getDatabase();
2488
+ db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
2489
+ }
2490
+ function addSessionTag(id, tag) {
2491
+ const db = getDatabase();
2492
+ db.prepare("INSERT OR IGNORE INTO session_tags (session_id, tag) VALUES (?, ?)").run(id, tag);
2493
+ return getSessionTags(id);
2494
+ }
2495
+ function removeSessionTag(id, tag) {
2496
+ const db = getDatabase();
2497
+ db.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(id, tag);
2498
+ return getSessionTags(id);
2499
+ }
2500
+ function getSessionTags(id) {
2501
+ const db = getDatabase();
2502
+ return db.query("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(id).map((r) => r.tag);
2503
+ }
2504
+ function listSessionsByTag(tag) {
2505
+ const db = getDatabase();
2506
+ return db.query("SELECT s.* FROM sessions s JOIN session_tags t ON s.id = t.session_id WHERE t.tag = ? ORDER BY s.created_at DESC").all(tag);
2507
+ }
2381
2508
  var init_sessions = __esm(() => {
2382
2509
  init_schema();
2383
2510
  init_types();
@@ -2403,10 +2530,52 @@ async function getPage(browser, options) {
2403
2530
  });
2404
2531
  return context.newPage();
2405
2532
  }
2406
- async function closeBrowser(browser) {
2407
- try {
2408
- await browser.close();
2409
- } catch {}
2533
+
2534
+ class BrowserPool {
2535
+ pool = [];
2536
+ maxSize;
2537
+ options;
2538
+ constructor(maxSize = 3, options) {
2539
+ this.maxSize = maxSize;
2540
+ this.options = options;
2541
+ }
2542
+ async acquire(headless = true) {
2543
+ const available = this.pool.find((e) => !e.inUse);
2544
+ if (available) {
2545
+ available.inUse = true;
2546
+ return available.browser;
2547
+ }
2548
+ if (this.pool.length < this.maxSize) {
2549
+ const browser = await launchPlaywright({ ...this.options, headless });
2550
+ this.pool.push({ browser, inUse: true, createdAt: Date.now() });
2551
+ return browser;
2552
+ }
2553
+ return new Promise((resolve) => {
2554
+ const interval = setInterval(() => {
2555
+ const free = this.pool.find((e) => !e.inUse);
2556
+ if (free) {
2557
+ clearInterval(interval);
2558
+ free.inUse = true;
2559
+ resolve(free.browser);
2560
+ }
2561
+ }, 100);
2562
+ });
2563
+ }
2564
+ release(browser) {
2565
+ const entry = this.pool.find((e) => e.browser === browser);
2566
+ if (entry)
2567
+ entry.inUse = false;
2568
+ }
2569
+ async destroyAll() {
2570
+ await Promise.all(this.pool.map((e) => e.browser.close().catch(() => {})));
2571
+ this.pool = [];
2572
+ }
2573
+ get size() {
2574
+ return this.pool.length;
2575
+ }
2576
+ get available() {
2577
+ return this.pool.filter((e) => !e.inUse).length;
2578
+ }
2410
2579
  }
2411
2580
  var DEFAULT_VIEWPORT;
2412
2581
  var init_playwright = __esm(() => {
@@ -3294,6 +3463,7 @@ __export(exports_session, {
3294
3463
  renameSession: () => renameSession2,
3295
3464
  listSessions: () => listSessions2,
3296
3465
  isBunSession: () => isBunSession,
3466
+ isAutoGallery: () => isAutoGallery,
3297
3467
  hasActiveHandle: () => hasActiveHandle,
3298
3468
  getTokenBudget: () => getTokenBudget,
3299
3469
  getSessionPage: () => getSessionPage,
@@ -3302,10 +3472,14 @@ __export(exports_session, {
3302
3472
  getSessionBunView: () => getSessionBunView,
3303
3473
  getSessionBrowser: () => getSessionBrowser,
3304
3474
  getSession: () => getSession2,
3475
+ getDefaultSession: () => getDefaultSession,
3305
3476
  getActiveSessions: () => getActiveSessions,
3477
+ getActiveSessionForAgent: () => getActiveSessionForAgent2,
3306
3478
  createSession: () => createSession2,
3479
+ countActiveSessions: () => countActiveSessions2,
3307
3480
  closeSession: () => closeSession2,
3308
- closeAllSessions: () => closeAllSessions
3481
+ closeAllSessions: () => closeAllSessions,
3482
+ browserPool: () => pool
3309
3483
  });
3310
3484
  function createBunProxy(view) {
3311
3485
  return view;
@@ -3335,11 +3509,7 @@ async function createSession2(opts = {}) {
3335
3509
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
3336
3510
  page = await context.newPage();
3337
3511
  } else {
3338
- browser = await launchPlaywright({
3339
- headless: opts.headless ?? true,
3340
- viewport: opts.viewport,
3341
- userAgent: opts.userAgent
3342
- });
3512
+ browser = await pool.acquire(opts.headless ?? true);
3343
3513
  page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
3344
3514
  }
3345
3515
  const sessionName = opts.name ?? (opts.startUrl ? (() => {
@@ -3393,7 +3563,7 @@ async function createSession2(opts = {}) {
3393
3563
  } catch {}
3394
3564
  }
3395
3565
  }
3396
- handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
3566
+ handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
3397
3567
  if (opts.startUrl) {
3398
3568
  try {
3399
3569
  if (bunView) {
@@ -3419,6 +3589,7 @@ function getSessionPage(sessionId) {
3419
3589
  handles.delete(sessionId);
3420
3590
  throw new SessionNotFoundError(sessionId);
3421
3591
  }
3592
+ handle.lastActivity = Date.now();
3422
3593
  return handle.page;
3423
3594
  }
3424
3595
  function getSessionBunView(sessionId) {
@@ -3466,10 +3637,8 @@ async function closeSession2(sessionId) {
3466
3637
  try {
3467
3638
  await handle.page.context().close();
3468
3639
  } catch {}
3469
- try {
3470
- if (handle.browser)
3471
- await closeBrowser(handle.browser);
3472
- } catch {}
3640
+ if (handle.browser)
3641
+ pool.release(handle.browser);
3473
3642
  }
3474
3643
  handles.delete(sessionId);
3475
3644
  }
@@ -3488,6 +3657,7 @@ async function closeAllSessions() {
3488
3657
  for (const [id] of handles) {
3489
3658
  await closeSession2(id).catch(() => {});
3490
3659
  }
3660
+ await pool.destroyAll();
3491
3661
  }
3492
3662
  function getSessionByName2(name) {
3493
3663
  return getSessionByName(name);
@@ -3499,7 +3669,49 @@ function getTokenBudget(sessionId) {
3499
3669
  const handle = handles.get(sessionId);
3500
3670
  return handle ? handle.tokenBudget : null;
3501
3671
  }
3502
- var handles;
3672
+ function getActiveSessionForAgent2(agentId) {
3673
+ const session = getActiveSessionForAgent(agentId);
3674
+ if (!session)
3675
+ return null;
3676
+ const handle = handles.get(session.id);
3677
+ if (!handle)
3678
+ return null;
3679
+ try {
3680
+ if (handle.bunView)
3681
+ handle.bunView.url();
3682
+ else
3683
+ handle.page.url();
3684
+ } catch {
3685
+ handles.delete(session.id);
3686
+ return null;
3687
+ }
3688
+ return { session, page: handle.page };
3689
+ }
3690
+ function getDefaultSession() {
3691
+ const session = getDefaultActiveSession();
3692
+ if (!session)
3693
+ return null;
3694
+ const handle = handles.get(session.id);
3695
+ if (!handle)
3696
+ return null;
3697
+ try {
3698
+ if (handle.bunView)
3699
+ handle.bunView.url();
3700
+ else
3701
+ handle.page.url();
3702
+ } catch {
3703
+ handles.delete(session.id);
3704
+ return null;
3705
+ }
3706
+ return { session, page: handle.page };
3707
+ }
3708
+ function isAutoGallery(sessionId) {
3709
+ return handles.get(sessionId)?.autoGallery ?? false;
3710
+ }
3711
+ function countActiveSessions2() {
3712
+ return countActiveSessions();
3713
+ }
3714
+ var handles, pool, SESSION_TTL_MS, ttlInterval;
3503
3715
  var init_session = __esm(() => {
3504
3716
  init_types();
3505
3717
  init_types();
@@ -3513,6 +3725,20 @@ var init_session = __esm(() => {
3513
3725
  init_stealth();
3514
3726
  init_dialogs();
3515
3727
  handles = new Map;
3728
+ pool = new BrowserPool(5);
3729
+ SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
3730
+ ttlInterval = setInterval(async () => {
3731
+ const now = Date.now();
3732
+ for (const [id, handle] of handles) {
3733
+ if (now - handle.lastActivity > SESSION_TTL_MS) {
3734
+ try {
3735
+ await closeSession2(id);
3736
+ } catch {}
3737
+ }
3738
+ }
3739
+ }, 60000);
3740
+ if (ttlInterval.unref)
3741
+ ttlInterval.unref();
3516
3742
  });
3517
3743
 
3518
3744
  // src/lib/snapshot.ts
@@ -15844,6 +16070,20 @@ async function persistFile(localPath, opts) {
15844
16070
  }
15845
16071
  var init_files_integration = () => {};
15846
16072
 
16073
+ // src/db/timeline.ts
16074
+ function logEvent(sessionId, eventType, details = {}) {
16075
+ const db = getDatabase();
16076
+ const id = crypto.randomUUID();
16077
+ db.prepare("INSERT INTO session_events (id, session_id, event_type, details) VALUES (?, ?, ?, ?)").run(id, sessionId, eventType, JSON.stringify(details));
16078
+ }
16079
+ function getTimeline(sessionId, limit = 100) {
16080
+ const db = getDatabase();
16081
+ return db.query("SELECT * FROM session_events WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
16082
+ }
16083
+ var init_timeline = __esm(() => {
16084
+ init_schema();
16085
+ });
16086
+
15847
16087
  // src/lib/tabs.ts
15848
16088
  async function newTab(page, url) {
15849
16089
  const context = page.context();
@@ -16235,6 +16475,7 @@ __export(exports_dist, {
16235
16475
  shortUuid: () => shortUuid,
16236
16476
  setFocus: () => setFocus,
16237
16477
  setActiveProfile: () => setActiveProfile,
16478
+ setActiveModel: () => setActiveModel,
16238
16479
  searchMemories: () => searchMemories,
16239
16480
  runCleanup: () => runCleanup,
16240
16481
  resolveProjectId: () => resolveProjectId,
@@ -16285,6 +16526,8 @@ __export(exports_dist, {
16285
16526
  getAutoMemoryStats: () => getAutoMemoryStats,
16286
16527
  getAgent: () => getAgent2,
16287
16528
  getActiveProfile: () => getActiveProfile,
16529
+ getActiveModel: () => getActiveModel,
16530
+ gatherTrainingData: () => gatherTrainingData,
16288
16531
  focusFilterSQL: () => focusFilterSQL,
16289
16532
  findPath: () => findPath,
16290
16533
  enforceQuotas: () => enforceQuotas,
@@ -16301,6 +16544,7 @@ __export(exports_dist, {
16301
16544
  containsSecrets: () => containsSecrets,
16302
16545
  configureAutoMemory: () => configureAutoMemory,
16303
16546
  closeDatabase: () => closeDatabase,
16547
+ clearActiveModel: () => clearActiveModel,
16304
16548
  cleanExpiredMemories: () => cleanExpiredMemories,
16305
16549
  cleanExpiredLocks: () => cleanExpiredLocks,
16306
16550
  checkMemoryWriteLock: () => checkMemoryWriteLock,
@@ -16321,6 +16565,7 @@ __export(exports_dist, {
16321
16565
  InvalidScopeError: () => InvalidScopeError,
16322
16566
  EntityNotFoundError: () => EntityNotFoundError,
16323
16567
  DuplicateMemoryError: () => DuplicateMemoryError,
16568
+ DEFAULT_MODEL: () => DEFAULT_MODEL,
16324
16569
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
16325
16570
  });
16326
16571
  import { Database as Database2 } from "bun:sqlite";
@@ -16332,6 +16577,9 @@ import { basename as basename2, dirname as dirname2, join as join22, resolve as
16332
16577
  import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
16333
16578
  import { homedir as homedir22 } from "os";
16334
16579
  import { join as join32 } from "path";
16580
+ import { existsSync as existsSync42, mkdirSync as mkdirSync42, readFileSync as readFileSync32, writeFileSync as writeFileSync32 } from "fs";
16581
+ import { homedir as homedir32 } from "os";
16582
+ import { join as join42 } from "path";
16335
16583
  function isInMemoryDb(path) {
16336
16584
  return path === ":memory:" || path.startsWith("file::memory:");
16337
16585
  }
@@ -19365,6 +19613,85 @@ function jaccardSimilarity(a, b) {
19365
19613
  const union = new Set([...a, ...b]).size;
19366
19614
  return intersection / union;
19367
19615
  }
19616
+ function memoryToRecallExample(memory) {
19617
+ return {
19618
+ messages: [
19619
+ { role: "system", content: SYSTEM_PROMPT },
19620
+ {
19621
+ role: "user",
19622
+ content: `What do you remember about "${memory.key}"?`
19623
+ },
19624
+ {
19625
+ role: "assistant",
19626
+ content: memory.summary ? `${memory.value}
19627
+
19628
+ Summary: ${memory.summary}` : memory.value
19629
+ }
19630
+ ]
19631
+ };
19632
+ }
19633
+ function memoryToSaveExample(memory) {
19634
+ const tags = memory.tags ?? [];
19635
+ return {
19636
+ messages: [
19637
+ { role: "system", content: SYSTEM_PROMPT },
19638
+ {
19639
+ role: "user",
19640
+ content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
19641
+ },
19642
+ {
19643
+ role: "assistant",
19644
+ content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
19645
+ }
19646
+ ]
19647
+ };
19648
+ }
19649
+ function memoryToSearchExample(memories, category) {
19650
+ const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
19651
+ return {
19652
+ messages: [
19653
+ { role: "system", content: SYSTEM_PROMPT },
19654
+ { role: "user", content: `What ${category} memories do you have?` },
19655
+ {
19656
+ role: "assistant",
19657
+ content: matched.length > 0 ? `Here are my ${category} memories:
19658
+ ${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
19659
+ `)}` : `I don't have any ${category} memories stored yet.`
19660
+ }
19661
+ ]
19662
+ };
19663
+ }
19664
+ function readConfig() {
19665
+ if (!existsSync42(CONFIG_PATH))
19666
+ return {};
19667
+ try {
19668
+ const raw = readFileSync32(CONFIG_PATH, "utf-8");
19669
+ return JSON.parse(raw);
19670
+ } catch {
19671
+ return {};
19672
+ }
19673
+ }
19674
+ function writeConfig(config) {
19675
+ if (!existsSync42(CONFIG_DIR)) {
19676
+ mkdirSync42(CONFIG_DIR, { recursive: true });
19677
+ }
19678
+ writeFileSync32(CONFIG_PATH, JSON.stringify(config, null, 2) + `
19679
+ `, "utf-8");
19680
+ }
19681
+ function getActiveModel() {
19682
+ const config = readConfig();
19683
+ return config.activeModel ?? DEFAULT_MODEL;
19684
+ }
19685
+ function setActiveModel(modelId) {
19686
+ const config = readConfig();
19687
+ config.activeModel = modelId;
19688
+ writeConfig(config);
19689
+ }
19690
+ function clearActiveModel() {
19691
+ const config = readConfig();
19692
+ delete config.activeModel;
19693
+ writeConfig(config);
19694
+ }
19368
19695
  var __defProp2, __export2 = (target, all) => {
19369
19696
  for (var name in all)
19370
19697
  __defProp2(target, name, {
@@ -19407,7 +19734,27 @@ Return JSON with this exact shape:
19407
19734
  "relations": [
19408
19735
  { "from": string, "to": string, "type": "uses"|"knows"|"depends_on"|"created_by"|"related_to"|"contradicts"|"part_of"|"implements"|"happened_before"|"happened_after"|"caused_by"|"resulted_in"|"supersedes"|"version_of" }
19409
19736
  ]
19410
- }`, ANTHROPIC_MODELS, AnthropicProvider, OpenAICompatProvider, OPENAI_MODELS, OpenAIProvider, CEREBRAS_MODELS, CerebrasProvider, GROK_MODELS, GrokProvider, providerRegistry, MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue, DEDUP_SIMILARITY_THRESHOLD = 0.85, DEFAULT_CONFIG2, _stats;
19737
+ }`, ANTHROPIC_MODELS, AnthropicProvider, OpenAICompatProvider, OPENAI_MODELS, OpenAIProvider, CEREBRAS_MODELS, CerebrasProvider, GROK_MODELS, GrokProvider, providerRegistry, MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue, DEDUP_SIMILARITY_THRESHOLD = 0.85, DEFAULT_CONFIG2, _stats, SYSTEM_PROMPT = "You are an AI assistant with persistent memory that recalls and saves information across sessions.", gatherTrainingData = async (options = {}) => {
19738
+ const allMemories = listMemories({ status: "active" });
19739
+ const filtered = options.since ? allMemories.filter((m) => new Date(m.created_at) >= options.since) : allMemories;
19740
+ const sorted = filtered.slice().sort((a, b) => b.importance - a.importance);
19741
+ const fetchSet = options.limit ? sorted.slice(0, options.limit * 3) : sorted;
19742
+ const examples = [];
19743
+ for (const memory of fetchSet) {
19744
+ examples.push(memoryToRecallExample(memory));
19745
+ examples.push(memoryToSaveExample(memory));
19746
+ }
19747
+ const categories = [...new Set(fetchSet.map((m) => m.category))];
19748
+ for (const category of categories) {
19749
+ examples.push(memoryToSearchExample(fetchSet, category));
19750
+ }
19751
+ const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
19752
+ return {
19753
+ source: "mementos",
19754
+ examples: finalExamples,
19755
+ count: finalExamples.length
19756
+ };
19757
+ }, DEFAULT_MODEL = "gpt-4o-mini", CONFIG_DIR, CONFIG_PATH;
19411
19758
  var init_dist = __esm(() => {
19412
19759
  __defProp2 = Object.defineProperty;
19413
19760
  exports_database = {};
@@ -20687,6 +21034,8 @@ Return only a number 0-10.`);
20687
21034
  keepLonger: true
20688
21035
  };
20689
21036
  _stats = { checked: 0, skipped: 0, updated: 0 };
21037
+ CONFIG_DIR = join42(homedir32(), ".mementos");
21038
+ CONFIG_PATH = join42(CONFIG_DIR, "config.json");
20690
21039
  });
20691
21040
 
20692
21041
  // src/lib/page-memory.ts
@@ -20847,13 +21196,13 @@ import { homedir as homedir10 } from "os";
20847
21196
  import { randomUUID as randomUUID10 } from "crypto";
20848
21197
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
20849
21198
  import { join as join33 } from "path";
20850
- import { homedir as homedir32 } from "os";
21199
+ import { homedir as homedir33 } from "os";
20851
21200
  import { readFileSync as readFileSync5 } from "fs";
20852
21201
  import { join as join23 } from "path";
20853
21202
  import { homedir as homedir23 } from "os";
20854
21203
  import { randomUUID as randomUUID22 } from "crypto";
20855
21204
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync33 } from "fs";
20856
- import { join as join42, dirname as dirname22 } from "path";
21205
+ import { join as join43, dirname as dirname22 } from "path";
20857
21206
  import { homedir as homedir42 } from "os";
20858
21207
  function getDbPath2() {
20859
21208
  if (process.env.CONVERSATIONS_DB_PATH)
@@ -21192,7 +21541,7 @@ function parseMessage(row) {
21192
21541
  function getAttachmentsDir() {
21193
21542
  if (process.env.CONVERSATIONS_ATTACHMENTS_DIR)
21194
21543
  return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
21195
- return join33(homedir32(), ".conversations", "attachments");
21544
+ return join33(homedir33(), ".conversations", "attachments");
21196
21545
  }
21197
21546
  function guessMimeType(name) {
21198
21547
  const ext = name.split(".").pop()?.toLowerCase();
@@ -24863,7 +25212,7 @@ Check the top-level render call using <` + parentName + ">.";
24863
25212
  "zinc-eagle",
24864
25213
  "zone-fox"
24865
25214
  ];
24866
- AGENT_ID_FILE = join42(homedir42(), ".conversations", "agent-id");
25215
+ AGENT_ID_FILE = join43(homedir42(), ".conversations", "agent-id");
24867
25216
  init_db();
24868
25217
  init_db();
24869
25218
  CONFLICT_THRESHOLD_SECONDS = 30 * 60;
@@ -25205,7 +25554,7 @@ __export(exports_dist3, {
25205
25554
  deleteTemplate: () => deleteTemplate,
25206
25555
  deleteTaskList: () => deleteTaskList,
25207
25556
  deleteTask: () => deleteTask,
25208
- deleteSession: () => deleteSession,
25557
+ deleteSession: () => deleteSession2,
25209
25558
  deleteProject: () => deleteProject2,
25210
25559
  deletePlan: () => deletePlan,
25211
25560
  deleteOrg: () => deleteOrg,
@@ -25272,11 +25621,11 @@ import { existsSync as existsSync33 } from "fs";
25272
25621
  import { join as join34 } from "path";
25273
25622
  import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
25274
25623
  import { join as join24 } from "path";
25275
- import { existsSync as existsSync42, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
25276
- import { join as join43 } from "path";
25624
+ import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
25625
+ import { join as join44 } from "path";
25277
25626
  import { existsSync as existsSync52 } from "fs";
25278
25627
  import { join as join52 } from "path";
25279
- import { readFileSync as readFileSync32, statSync as statSync22 } from "fs";
25628
+ import { readFileSync as readFileSync33, statSync as statSync22 } from "fs";
25280
25629
  import { relative, resolve as resolve22, join as join62 } from "path";
25281
25630
  import { execSync as execSync2 } from "child_process";
25282
25631
 
@@ -27634,11 +27983,11 @@ function autoReleaseStaleAgents(db2) {
27634
27983
  const result = d.run("UPDATE agents SET session_id = NULL WHERE status = 'active' AND session_id IS NOT NULL AND last_seen_at < ?", [cutoff]);
27635
27984
  return result.changes;
27636
27985
  }
27637
- function getAvailableNamesFromPool(pool, db2) {
27986
+ function getAvailableNamesFromPool(pool2, db2) {
27638
27987
  autoReleaseStaleAgents(db2);
27639
27988
  const cutoff = new Date(Date.now() - getActiveWindowMs()).toISOString();
27640
27989
  const activeNames = new Set(db2.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
27641
- return pool.filter((name) => !activeNames.has(name.toLowerCase()));
27990
+ return pool2.filter((name) => !activeNames.has(name.toLowerCase()));
27642
27991
  }
27643
27992
  function shortUuid2() {
27644
27993
  return crypto.randomUUID().slice(0, 8);
@@ -27715,9 +28064,9 @@ function registerAgent5(input, db2) {
27715
28064
  function isAgentConflict2(result) {
27716
28065
  return result.conflict === true;
27717
28066
  }
27718
- function buildConflictError(existing, lastSeenMs, pool, d) {
28067
+ function buildConflictError(existing, lastSeenMs, pool2, d) {
27719
28068
  const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
27720
- const suggestions = pool ? getAvailableNamesFromPool(pool, d) : [];
28069
+ const suggestions = pool2 ? getAvailableNamesFromPool(pool2, d) : [];
27721
28070
  return {
27722
28071
  conflict: true,
27723
28072
  existing_id: existing.id,
@@ -28003,7 +28352,7 @@ function updateSessionActivity(id, db2) {
28003
28352
  const d = db2 || getDatabase3();
28004
28353
  d.run("UPDATE sessions SET last_activity = ? WHERE id = ?", [now2(), id]);
28005
28354
  }
28006
- function deleteSession(id, db2) {
28355
+ function deleteSession2(id, db2) {
28007
28356
  const d = db2 || getDatabase3();
28008
28357
  const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
28009
28358
  return result.changes > 0;
@@ -28822,13 +29171,13 @@ function searchTasks(options, projectId, taskListId, db2) {
28822
29171
  return rows.map(rowToTask3);
28823
29172
  }
28824
29173
  function getTaskListDir(taskListId) {
28825
- return join43(HOME, ".claude", "tasks", taskListId);
29174
+ return join44(HOME, ".claude", "tasks", taskListId);
28826
29175
  }
28827
29176
  function readClaudeTask(dir, filename) {
28828
- return readJsonFile(join43(dir, filename));
29177
+ return readJsonFile(join44(dir, filename));
28829
29178
  }
28830
29179
  function writeClaudeTask(dir, task) {
28831
- writeJsonFile(join43(dir, `${task.id}.json`), task);
29180
+ writeJsonFile(join44(dir, `${task.id}.json`), task);
28832
29181
  }
28833
29182
  function toClaudeStatus(status) {
28834
29183
  if (status === "pending" || status === "in_progress" || status === "completed") {
@@ -28840,14 +29189,14 @@ function toSqliteStatus(status) {
28840
29189
  return status;
28841
29190
  }
28842
29191
  function readPrefixCounter(dir) {
28843
- const path = join43(dir, ".prefix-counter");
28844
- if (!existsSync42(path))
29192
+ const path = join44(dir, ".prefix-counter");
29193
+ if (!existsSync43(path))
28845
29194
  return 0;
28846
29195
  const val = parseInt(readFileSync24(path, "utf-8").trim(), 10);
28847
29196
  return isNaN(val) ? 0 : val;
28848
29197
  }
28849
29198
  function writePrefixCounter(dir, value) {
28850
- writeFileSync23(join43(dir, ".prefix-counter"), String(value));
29199
+ writeFileSync23(join44(dir, ".prefix-counter"), String(value));
28851
29200
  }
28852
29201
  function formatPrefixedSubject(title, prefix, counter) {
28853
29202
  const padded = String(counter).padStart(5, "0");
@@ -28874,7 +29223,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
28874
29223
  }
28875
29224
  function pushToClaudeTaskList(taskListId, projectId, options = {}) {
28876
29225
  const dir = getTaskListDir(taskListId);
28877
- if (!existsSync42(dir))
29226
+ if (!existsSync43(dir))
28878
29227
  ensureDir22(dir);
28879
29228
  const filter = {};
28880
29229
  if (projectId)
@@ -28883,7 +29232,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
28883
29232
  const existingByTodosId = new Map;
28884
29233
  const files = listJsonFiles(dir);
28885
29234
  for (const f of files) {
28886
- const path = join43(dir, f);
29235
+ const path = join44(dir, f);
28887
29236
  const ct = readClaudeTask(dir, f);
28888
29237
  if (ct?.metadata?.["todos_id"]) {
28889
29238
  existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
@@ -28970,7 +29319,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
28970
29319
  }
28971
29320
  function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
28972
29321
  const dir = getTaskListDir(taskListId);
28973
- if (!existsSync42(dir)) {
29322
+ if (!existsSync43(dir)) {
28974
29323
  return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
28975
29324
  }
28976
29325
  const files = readdirSync22(dir).filter((f) => f.endsWith(".json"));
@@ -28990,7 +29339,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
28990
29339
  }
28991
29340
  for (const f of files) {
28992
29341
  try {
28993
- const filePath = join43(dir, f);
29342
+ const filePath = join44(dir, f);
28994
29343
  const ct = readClaudeTask(dir, f);
28995
29344
  if (!ct)
28996
29345
  continue;
@@ -29401,7 +29750,7 @@ function extractTodos(options, db2) {
29401
29750
  for (const file of files) {
29402
29751
  const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
29403
29752
  try {
29404
- const source = readFileSync32(fullPath, "utf-8");
29753
+ const source = readFileSync33(fullPath, "utf-8");
29405
29754
  const relPath = statSync22(basePath).isFile() ? relative(resolve22(basePath, ".."), fullPath) : file;
29406
29755
  const comments = extractFromSource(source, relPath, tags);
29407
29756
  allComments.push(...comments);
@@ -30627,7 +30976,7 @@ import { homedir as homedir11 } from "os";
30627
30976
  import { fileURLToPath } from "url";
30628
30977
  import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
30629
30978
  import { join as join25 } from "path";
30630
- import { existsSync as existsSync34, readFileSync as readFileSync33, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25 } from "fs";
30979
+ import { existsSync as existsSync34, readFileSync as readFileSync34, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25 } from "fs";
30631
30980
  import { join as join35, dirname as dirname23 } from "path";
30632
30981
  import { homedir as homedir24 } from "os";
30633
30982
  function getSkillsByCategory(category) {
@@ -31281,7 +31630,7 @@ function readConfigFile(path) {
31281
31630
  if (!existsSync34(path))
31282
31631
  return {};
31283
31632
  try {
31284
- const raw = readFileSync33(path, "utf-8");
31633
+ const raw = readFileSync34(path, "utf-8");
31285
31634
  const parsed = JSON.parse(raw);
31286
31635
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
31287
31636
  return {};
@@ -31314,7 +31663,7 @@ function saveConfig(key, value, scope = "project") {
31314
31663
  let existing = {};
31315
31664
  if (existsSync34(filePath)) {
31316
31665
  try {
31317
- existing = JSON.parse(readFileSync33(filePath, "utf-8"));
31666
+ existing = JSON.parse(readFileSync34(filePath, "utf-8"));
31318
31667
  if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
31319
31668
  existing = {};
31320
31669
  }
@@ -33256,7 +33605,7 @@ ${snap.tree.slice(0, 2000)}`;
33256
33605
  const response = await client.messages.create({
33257
33606
  model,
33258
33607
  max_tokens: 512,
33259
- system: SYSTEM_PROMPT,
33608
+ system: SYSTEM_PROMPT2,
33260
33609
  messages: [{
33261
33610
  role: "user",
33262
33611
  content: `Task: ${task}
@@ -33321,7 +33670,7 @@ What actions should I take next? Return JSON array.`
33321
33670
  }
33322
33671
  return { success: false, result: null, steps_taken: steps.length, steps, cost_estimate: totalTokens / 1000 * 0.00025, error: `Reached max steps (${maxSteps}) without completing task` };
33323
33672
  }
33324
- var SYSTEM_PROMPT = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
33673
+ var SYSTEM_PROMPT2 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
33325
33674
 
33326
33675
  Return a JSON array of at most 3 actions to execute next:
33327
33676
  [{"tool": "navigate|click|type|scroll|evaluate|done", "args": {...}, "reason": "..."}]
@@ -33347,6 +33696,34 @@ function err(e) {
33347
33696
  isError: true
33348
33697
  };
33349
33698
  }
33699
+ async function errWithScreenshot(e, sessionId) {
33700
+ const msg = e instanceof Error ? e.message : String(e);
33701
+ const code = e instanceof BrowserError ? e.code : "ERROR";
33702
+ let screenshot_path;
33703
+ if (sessionId) {
33704
+ try {
33705
+ const sid = resolveSessionId(sessionId);
33706
+ const page = getSessionPage(sid);
33707
+ const result = await takeScreenshot(page, { maxWidth: 800, quality: 50, track: false, thumbnail: false });
33708
+ screenshot_path = result.path;
33709
+ } catch {}
33710
+ }
33711
+ return {
33712
+ content: [{ type: "text", text: JSON.stringify({ error: msg, code, error_screenshot: screenshot_path }) }],
33713
+ isError: true
33714
+ };
33715
+ }
33716
+ function resolveSessionId(sessionId) {
33717
+ if (sessionId)
33718
+ return sessionId;
33719
+ const def = getDefaultSession();
33720
+ if (def)
33721
+ return def.session.id;
33722
+ const count = countActiveSessions2();
33723
+ if (count === 0)
33724
+ throw new BrowserError("No active sessions. Create one with browser_session_create first.", "NO_SESSION");
33725
+ throw new BrowserError(`${count} active sessions \u2014 specify session_id to choose one.`, "AMBIGUOUS_SESSION");
33726
+ }
33350
33727
  var _pkg, networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles2, _startupToolCount, transport;
33351
33728
  var init_mcp = __esm(async () => {
33352
33729
  init_zod();
@@ -33368,6 +33745,7 @@ var init_mcp = __esm(async () => {
33368
33745
  init_snapshot();
33369
33746
  init_files_integration();
33370
33747
  init_recordings();
33748
+ init_timeline();
33371
33749
  init_dialogs();
33372
33750
  init_profiles();
33373
33751
  init_types();
@@ -33379,7 +33757,7 @@ var init_mcp = __esm(async () => {
33379
33757
  name: "@hasna/browser",
33380
33758
  version: "0.0.1"
33381
33759
  });
33382
- server.tool("browser_session_create", "Create a new browser session with the specified engine", {
33760
+ server.tool("browser_session_create", "Create a new browser session. If agent_id is set and already has an active session, returns the existing one (use force_new to override). If session_id is omitted on other tools, the single active session is auto-selected.", {
33383
33761
  engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
33384
33762
  use_case: exports_external.string().optional(),
33385
33763
  project_id: exports_external.string().optional(),
@@ -33388,9 +33766,17 @@ var init_mcp = __esm(async () => {
33388
33766
  headless: exports_external.boolean().optional().default(true),
33389
33767
  viewport_width: exports_external.number().optional().default(1280),
33390
33768
  viewport_height: exports_external.number().optional().default(720),
33391
- stealth: exports_external.boolean().optional().default(false)
33392
- }, async ({ engine, use_case, project_id, agent_id, start_url, headless, viewport_width, viewport_height, stealth }) => {
33769
+ stealth: exports_external.boolean().optional().default(false),
33770
+ auto_gallery: exports_external.boolean().optional().default(false),
33771
+ force_new: exports_external.boolean().optional().default(false).describe("Force create a new session even if agent already has one"),
33772
+ tags: exports_external.array(exports_external.string()).optional()
33773
+ }, async ({ engine, use_case, project_id, agent_id, start_url, headless, viewport_width, viewport_height, stealth, auto_gallery, force_new, tags }) => {
33393
33774
  try {
33775
+ if (agent_id && !force_new) {
33776
+ const existing = getActiveSessionForAgent2(agent_id);
33777
+ if (existing)
33778
+ return json({ session: existing.session, reused: true });
33779
+ }
33394
33780
  const { session } = await createSession2({
33395
33781
  engine,
33396
33782
  useCase: use_case,
@@ -33399,44 +33785,66 @@ var init_mcp = __esm(async () => {
33399
33785
  startUrl: start_url,
33400
33786
  headless,
33401
33787
  viewport: { width: viewport_width, height: viewport_height },
33402
- stealth
33788
+ stealth,
33789
+ autoGallery: auto_gallery
33403
33790
  });
33404
- return json({ session });
33791
+ if (tags?.length) {
33792
+ const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
33793
+ for (const tag of tags)
33794
+ addSessionTag2(session.id, tag);
33795
+ }
33796
+ logEvent(session.id, "session_created", { engine: session.engine });
33797
+ return json({ session, reused: false });
33405
33798
  } catch (e) {
33406
33799
  return err(e);
33407
33800
  }
33408
33801
  });
33409
- server.tool("browser_session_list", "List all browser sessions", { status: exports_external.enum(["active", "closed", "error"]).optional(), project_id: exports_external.string().optional() }, async ({ status, project_id }) => {
33802
+ server.tool("browser_session_list", "List all browser sessions. Optionally filter by tag.", { status: exports_external.enum(["active", "closed", "error"]).optional(), project_id: exports_external.string().optional(), tag: exports_external.string().optional() }, async ({ status, project_id, tag }) => {
33410
33803
  try {
33804
+ if (tag) {
33805
+ const { listSessionsByTag: listSessionsByTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
33806
+ return json({ sessions: listSessionsByTag2(tag) });
33807
+ }
33411
33808
  return json({ sessions: listSessions2({ status, projectId: project_id }) });
33412
33809
  } catch (e) {
33413
33810
  return err(e);
33414
33811
  }
33415
33812
  });
33416
- server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string() }, async ({ session_id }) => {
33813
+ server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33417
33814
  try {
33418
- const session = await closeSession2(session_id);
33419
- networkLogCleanup.get(session_id)?.();
33420
- consoleCaptureCleanup.get(session_id)?.();
33421
- networkLogCleanup.delete(session_id);
33422
- consoleCaptureCleanup.delete(session_id);
33423
- harCaptures.delete(session_id);
33815
+ const sid = resolveSessionId(session_id);
33816
+ const session = await closeSession2(sid);
33817
+ networkLogCleanup.get(sid)?.();
33818
+ consoleCaptureCleanup.get(sid)?.();
33819
+ networkLogCleanup.delete(sid);
33820
+ consoleCaptureCleanup.delete(sid);
33821
+ harCaptures.delete(sid);
33424
33822
  return json({ session });
33425
33823
  } catch (e) {
33426
33824
  return err(e);
33427
33825
  }
33428
33826
  });
33827
+ server.tool("browser_session_timeline", "Get chronological action log for a session", { session_id: exports_external.string().optional(), limit: exports_external.number().optional().default(50) }, async ({ session_id, limit }) => {
33828
+ try {
33829
+ const sid = resolveSessionId(session_id);
33830
+ const events = getTimeline(sid, limit);
33831
+ return json({ events, count: events.length });
33832
+ } catch (e) {
33833
+ return err(e);
33834
+ }
33835
+ });
33429
33836
  server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
33430
- session_id: exports_external.string(),
33837
+ session_id: exports_external.string().optional(),
33431
33838
  url: exports_external.string(),
33432
33839
  timeout: exports_external.number().optional().default(30000),
33433
33840
  auto_snapshot: exports_external.boolean().optional().default(true),
33434
33841
  auto_thumbnail: exports_external.boolean().optional().default(true)
33435
33842
  }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
33436
33843
  try {
33437
- const page = getSessionPage(session_id);
33438
- if (isBunSession(session_id)) {
33439
- const bunView = getSessionBunView(session_id);
33844
+ const sid = resolveSessionId(session_id);
33845
+ const page = getSessionPage(sid);
33846
+ if (isBunSession(sid)) {
33847
+ const bunView = getSessionBunView(sid);
33440
33848
  await bunView.goto(url, { timeout });
33441
33849
  await new Promise((r) => setTimeout(r, 500));
33442
33850
  } else {
@@ -33463,10 +33871,10 @@ var init_mcp = __esm(async () => {
33463
33871
  } catch {}
33464
33872
  }
33465
33873
  try {
33466
- const session = getSession2(session_id);
33874
+ const session = getSession2(sid);
33467
33875
  if (!session.name) {
33468
33876
  const hostname = new URL(current_url).hostname;
33469
- renameSession2(session_id, hostname);
33877
+ renameSession2(sid, hostname);
33470
33878
  }
33471
33879
  } catch {}
33472
33880
  const result = {
@@ -33482,86 +33890,104 @@ var init_mcp = __esm(async () => {
33482
33890
  result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
33483
33891
  } catch {}
33484
33892
  }
33485
- if (isBunSession(session_id) && auto_snapshot) {
33893
+ if (isAutoGallery(sid)) {
33894
+ try {
33895
+ const ss = await takeScreenshot(page, { maxWidth: 1280, quality: 70, thumbnail: true });
33896
+ const { createEntry: createEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
33897
+ createEntry2({ session_id: sid, url: current_url, title, path: ss.path, thumbnail_path: ss.thumbnail_path, format: "webp", width: ss.width, height: ss.height, original_size_bytes: ss.original_size_bytes, compressed_size_bytes: ss.compressed_size_bytes, compression_ratio: ss.compression_ratio, tags: [], is_favorite: false });
33898
+ } catch {}
33899
+ }
33900
+ if (isBunSession(sid) && auto_snapshot) {
33486
33901
  await new Promise((r) => setTimeout(r, 200));
33487
33902
  }
33488
33903
  if (auto_snapshot) {
33489
33904
  try {
33490
- const snap = await takeSnapshot(page, session_id);
33491
- setLastSnapshot(session_id, snap);
33905
+ const snap = await takeSnapshot(page, sid);
33906
+ setLastSnapshot(sid, snap);
33492
33907
  const refEntries = Object.entries(snap.refs).slice(0, 30);
33493
33908
  result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
33494
33909
  result.interactive_count = snap.interactive_count;
33495
- result.has_errors = getConsoleLog(session_id, "error").length > 0;
33910
+ result.has_errors = getConsoleLog(sid, "error").length > 0;
33496
33911
  } catch {}
33497
33912
  }
33913
+ logEvent(sid, "navigate", { url, title, current_url });
33498
33914
  return json(result);
33499
33915
  } catch (e) {
33500
- return err(e);
33916
+ return errWithScreenshot(e, session_id);
33501
33917
  }
33502
33918
  });
33503
- server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
33919
+ server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33504
33920
  try {
33505
- const page = getSessionPage(session_id);
33921
+ const sid = resolveSessionId(session_id);
33922
+ const page = getSessionPage(sid);
33506
33923
  await goBack(page);
33507
33924
  return json({ url: page.url() });
33508
33925
  } catch (e) {
33509
33926
  return err(e);
33510
33927
  }
33511
33928
  });
33512
- server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
33929
+ server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33513
33930
  try {
33514
- const page = getSessionPage(session_id);
33931
+ const sid = resolveSessionId(session_id);
33932
+ const page = getSessionPage(sid);
33515
33933
  await goForward(page);
33516
33934
  return json({ url: page.url() });
33517
33935
  } catch (e) {
33518
33936
  return err(e);
33519
33937
  }
33520
33938
  });
33521
- server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
33939
+ server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33522
33940
  try {
33523
- const page = getSessionPage(session_id);
33941
+ const sid = resolveSessionId(session_id);
33942
+ const page = getSessionPage(sid);
33524
33943
  await reload(page);
33525
33944
  return json({ url: page.url() });
33526
33945
  } catch (e) {
33527
33946
  return err(e);
33528
33947
  }
33529
33948
  });
33530
- server.tool("browser_click", "Click an element by ref (from snapshot) or CSS selector. Prefer ref for reliability.", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), button: exports_external.enum(["left", "right", "middle"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, ref, button, timeout }) => {
33949
+ server.tool("browser_click", "Click an element by ref (from snapshot) or CSS selector. Prefer ref for reliability.", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), button: exports_external.enum(["left", "right", "middle"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, ref, button, timeout }) => {
33531
33950
  try {
33532
- const page = getSessionPage(session_id);
33951
+ const sid = resolveSessionId(session_id);
33952
+ const page = getSessionPage(sid);
33533
33953
  if (ref) {
33534
- await clickRef(page, session_id, ref, { timeout });
33954
+ await clickRef(page, sid, ref, { timeout });
33955
+ logEvent(sid, "click", { selector: ref, method: "ref" });
33535
33956
  return json({ clicked: ref, method: "ref" });
33536
33957
  }
33537
33958
  if (!selector)
33538
33959
  return err(new Error("Either ref or selector is required"));
33539
33960
  await click(page, selector, { button, timeout });
33961
+ logEvent(sid, "click", { selector, method: "selector" });
33540
33962
  return json({ clicked: selector, method: "selector" });
33541
33963
  } catch (e) {
33542
- return err(e);
33964
+ return errWithScreenshot(e, session_id);
33543
33965
  }
33544
33966
  });
33545
- server.tool("browser_type", "Type text into an element by ref or selector. Prefer ref.", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), text: exports_external.string(), clear: exports_external.boolean().optional().default(false), delay: exports_external.number().optional() }, async ({ session_id, selector, ref, text, clear, delay }) => {
33967
+ server.tool("browser_type", "Type text into an element by ref or selector. Prefer ref.", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), text: exports_external.string(), clear: exports_external.boolean().optional().default(false), delay: exports_external.number().optional() }, async ({ session_id, selector, ref, text, clear, delay }) => {
33546
33968
  try {
33547
- const page = getSessionPage(session_id);
33969
+ const sid = resolveSessionId(session_id);
33970
+ const page = getSessionPage(sid);
33548
33971
  if (ref) {
33549
- await typeRef(page, session_id, ref, text, { clear, delay });
33972
+ await typeRef(page, sid, ref, text, { clear, delay });
33973
+ logEvent(sid, "type", { selector: ref, text: text.slice(0, 100) });
33550
33974
  return json({ typed: text, ref, method: "ref" });
33551
33975
  }
33552
33976
  if (!selector)
33553
33977
  return err(new Error("Either ref or selector is required"));
33554
33978
  await type(page, selector, text, { clear, delay });
33979
+ logEvent(sid, "type", { selector, text: text.slice(0, 100) });
33555
33980
  return json({ typed: text, selector, method: "selector" });
33556
33981
  } catch (e) {
33557
- return err(e);
33982
+ return errWithScreenshot(e, session_id);
33558
33983
  }
33559
33984
  });
33560
- server.tool("browser_hover", "Hover over an element by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional() }, async ({ session_id, selector, ref }) => {
33985
+ server.tool("browser_hover", "Hover over an element by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional() }, async ({ session_id, selector, ref }) => {
33561
33986
  try {
33562
- const page = getSessionPage(session_id);
33987
+ const sid = resolveSessionId(session_id);
33988
+ const page = getSessionPage(sid);
33563
33989
  if (ref) {
33564
- await hoverRef(page, session_id, ref);
33990
+ await hoverRef(page, sid, ref);
33565
33991
  return json({ hovered: ref, method: "ref" });
33566
33992
  }
33567
33993
  if (!selector)
@@ -33572,20 +33998,22 @@ var init_mcp = __esm(async () => {
33572
33998
  return err(e);
33573
33999
  }
33574
34000
  });
33575
- server.tool("browser_scroll", "Scroll the page", { session_id: exports_external.string(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount }) => {
34001
+ server.tool("browser_scroll", "Scroll the page", { session_id: exports_external.string().optional(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount }) => {
33576
34002
  try {
33577
- const page = getSessionPage(session_id);
34003
+ const sid = resolveSessionId(session_id);
34004
+ const page = getSessionPage(sid);
33578
34005
  await scroll(page, direction, amount);
33579
34006
  return json({ scrolled: direction, amount });
33580
34007
  } catch (e) {
33581
34008
  return err(e);
33582
34009
  }
33583
34010
  });
33584
- server.tool("browser_select", "Select a dropdown option by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), value: exports_external.string() }, async ({ session_id, selector, ref, value }) => {
34011
+ server.tool("browser_select", "Select a dropdown option by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), value: exports_external.string() }, async ({ session_id, selector, ref, value }) => {
33585
34012
  try {
33586
- const page = getSessionPage(session_id);
34013
+ const sid = resolveSessionId(session_id);
34014
+ const page = getSessionPage(sid);
33587
34015
  if (ref) {
33588
- const selected2 = await selectRef(page, session_id, ref, value);
34016
+ const selected2 = await selectRef(page, sid, ref, value);
33589
34017
  return json({ selected: selected2, method: "ref" });
33590
34018
  }
33591
34019
  if (!selector)
@@ -33596,11 +34024,12 @@ var init_mcp = __esm(async () => {
33596
34024
  return err(e);
33597
34025
  }
33598
34026
  });
33599
- server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
34027
+ server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
33600
34028
  try {
33601
- const page = getSessionPage(session_id);
34029
+ const sid = resolveSessionId(session_id);
34030
+ const page = getSessionPage(sid);
33602
34031
  if (ref) {
33603
- await checkRef(page, session_id, ref, checked);
34032
+ await checkRef(page, sid, ref, checked);
33604
34033
  return json({ checked, ref, method: "ref" });
33605
34034
  }
33606
34035
  if (!selector)
@@ -33611,52 +34040,58 @@ var init_mcp = __esm(async () => {
33611
34040
  return err(e);
33612
34041
  }
33613
34042
  });
33614
- server.tool("browser_upload", "Upload a file to an input element", { session_id: exports_external.string(), selector: exports_external.string(), file_path: exports_external.string() }, async ({ session_id, selector, file_path }) => {
34043
+ server.tool("browser_upload", "Upload a file to an input element", { session_id: exports_external.string().optional(), selector: exports_external.string(), file_path: exports_external.string() }, async ({ session_id, selector, file_path }) => {
33615
34044
  try {
33616
- const page = getSessionPage(session_id);
34045
+ const sid = resolveSessionId(session_id);
34046
+ const page = getSessionPage(sid);
33617
34047
  await uploadFile(page, selector, file_path);
33618
34048
  return json({ uploaded: file_path, selector });
33619
34049
  } catch (e) {
33620
34050
  return err(e);
33621
34051
  }
33622
34052
  });
33623
- server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string(), key: exports_external.string() }, async ({ session_id, key }) => {
34053
+ server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string().optional(), key: exports_external.string() }, async ({ session_id, key }) => {
33624
34054
  try {
33625
- const page = getSessionPage(session_id);
34055
+ const sid = resolveSessionId(session_id);
34056
+ const page = getSessionPage(sid);
33626
34057
  await pressKey(page, key);
33627
34058
  return json({ pressed: key });
33628
34059
  } catch (e) {
33629
34060
  return err(e);
33630
34061
  }
33631
34062
  });
33632
- server.tool("browser_wait", "Wait for a selector to appear", { session_id: exports_external.string(), selector: exports_external.string(), state: exports_external.enum(["attached", "detached", "visible", "hidden"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, state, timeout }) => {
34063
+ server.tool("browser_wait", "Wait for a selector to appear", { session_id: exports_external.string().optional(), selector: exports_external.string(), state: exports_external.enum(["attached", "detached", "visible", "hidden"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, state, timeout }) => {
33633
34064
  try {
33634
- const page = getSessionPage(session_id);
34065
+ const sid = resolveSessionId(session_id);
34066
+ const page = getSessionPage(sid);
33635
34067
  await waitForSelector(page, selector, { state, timeout });
33636
34068
  return json({ ready: selector });
33637
34069
  } catch (e) {
33638
34070
  return err(e);
33639
34071
  }
33640
34072
  });
33641
- server.tool("browser_get_text", "Get text content from the page or a selector", { session_id: exports_external.string(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
34073
+ server.tool("browser_get_text", "Get text content from the page or a selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
33642
34074
  try {
33643
- const page = getSessionPage(session_id);
34075
+ const sid = resolveSessionId(session_id);
34076
+ const page = getSessionPage(sid);
33644
34077
  return json({ text: await getText(page, selector) });
33645
34078
  } catch (e) {
33646
34079
  return err(e);
33647
34080
  }
33648
34081
  });
33649
- server.tool("browser_get_html", "Get HTML content from the page or a selector", { session_id: exports_external.string(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
34082
+ server.tool("browser_get_html", "Get HTML content from the page or a selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
33650
34083
  try {
33651
- const page = getSessionPage(session_id);
34084
+ const sid = resolveSessionId(session_id);
34085
+ const page = getSessionPage(sid);
33652
34086
  return json({ html: await getHTML(page, selector) });
33653
34087
  } catch (e) {
33654
34088
  return err(e);
33655
34089
  }
33656
34090
  });
33657
- server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
34091
+ server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33658
34092
  try {
33659
- const page = getSessionPage(session_id);
34093
+ const sid = resolveSessionId(session_id);
34094
+ const page = getSessionPage(sid);
33660
34095
  const links = await getLinks(page);
33661
34096
  return json({ links, count: links.length });
33662
34097
  } catch (e) {
@@ -33664,22 +34099,24 @@ var init_mcp = __esm(async () => {
33664
34099
  }
33665
34100
  });
33666
34101
  server.tool("browser_extract", "Extract content from the page in a specified format", {
33667
- session_id: exports_external.string(),
34102
+ session_id: exports_external.string().optional(),
33668
34103
  format: exports_external.enum(["text", "html", "links", "table", "structured"]).optional().default("text"),
33669
34104
  selector: exports_external.string().optional(),
33670
34105
  schema: exports_external.record(exports_external.string()).optional()
33671
34106
  }, async ({ session_id, format, selector, schema }) => {
33672
34107
  try {
33673
- const page = getSessionPage(session_id);
34108
+ const sid = resolveSessionId(session_id);
34109
+ const page = getSessionPage(sid);
33674
34110
  const result = await extract(page, { format, selector, schema });
33675
34111
  return json(result);
33676
34112
  } catch (e) {
33677
34113
  return err(e);
33678
34114
  }
33679
34115
  });
33680
- server.tool("browser_find", "Find elements matching a selector and return their text", { session_id: exports_external.string(), selector: exports_external.string() }, async ({ session_id, selector }) => {
34116
+ server.tool("browser_find", "Find elements matching a selector and return their text", { session_id: exports_external.string().optional(), selector: exports_external.string() }, async ({ session_id, selector }) => {
33681
34117
  try {
33682
- const page = getSessionPage(session_id);
34118
+ const sid = resolveSessionId(session_id);
34119
+ const page = getSessionPage(sid);
33683
34120
  const elements = await findElements(page, selector);
33684
34121
  const texts = await Promise.all(elements.map((el) => el.textContent()));
33685
34122
  return json({ count: elements.length, texts });
@@ -33688,15 +34125,16 @@ var init_mcp = __esm(async () => {
33688
34125
  }
33689
34126
  });
33690
34127
  server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
33691
- session_id: exports_external.string(),
34128
+ session_id: exports_external.string().optional(),
33692
34129
  compact: exports_external.boolean().optional().default(true),
33693
34130
  max_refs: exports_external.number().optional().default(50),
33694
34131
  full_tree: exports_external.boolean().optional().default(false)
33695
34132
  }, async ({ session_id, compact, max_refs, full_tree }) => {
33696
34133
  try {
33697
- const page = getSessionPage(session_id);
33698
- const result = await takeSnapshot(page, session_id);
33699
- setLastSnapshot(session_id, result);
34134
+ const sid = resolveSessionId(session_id);
34135
+ const page = getSessionPage(sid);
34136
+ const result = await takeSnapshot(page, sid);
34137
+ setLastSnapshot(sid, result);
33700
34138
  const refEntries = Object.entries(result.refs).slice(0, max_refs);
33701
34139
  const limitedRefs = Object.fromEntries(refEntries);
33702
34140
  const truncated = Object.keys(result.refs).length > max_refs;
@@ -33719,21 +34157,22 @@ var init_mcp = __esm(async () => {
33719
34157
  }
33720
34158
  });
33721
34159
  server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overlay numbered labels on interactive elements for visual+ref workflows.", {
33722
- session_id: exports_external.string(),
34160
+ session_id: exports_external.string().optional(),
33723
34161
  selector: exports_external.string().optional(),
33724
34162
  full_page: exports_external.boolean().optional().default(false),
33725
34163
  format: exports_external.enum(["png", "jpeg", "webp"]).optional().default("webp"),
33726
- quality: exports_external.number().optional(),
33727
- max_width: exports_external.number().optional().default(1280),
34164
+ quality: exports_external.number().optional().default(60),
34165
+ max_width: exports_external.number().optional().default(800),
33728
34166
  compress: exports_external.boolean().optional().default(true),
33729
34167
  thumbnail: exports_external.boolean().optional().default(true),
33730
34168
  annotate: exports_external.boolean().optional().default(false)
33731
34169
  }, async ({ session_id, selector, full_page, format, quality, max_width, compress, thumbnail, annotate }) => {
33732
34170
  try {
33733
- const page = getSessionPage(session_id);
34171
+ const sid = resolveSessionId(session_id);
34172
+ const page = getSessionPage(sid);
33734
34173
  if (annotate && !selector && !full_page) {
33735
34174
  const { annotateScreenshot: annotateScreenshot2 } = await Promise.resolve().then(() => (init_annotate(), exports_annotate));
33736
- const annotated = await annotateScreenshot2(page, session_id);
34175
+ const annotated = await annotateScreenshot2(page, sid);
33737
34176
  const base64 = annotated.buffer.toString("base64");
33738
34177
  return json({
33739
34178
  base64: base64.length > 50000 ? undefined : base64,
@@ -33749,32 +34188,35 @@ var init_mcp = __esm(async () => {
33749
34188
  try {
33750
34189
  const buf = Buffer.from(result.base64, "base64");
33751
34190
  const filename = result.path.split("/").pop() ?? `screenshot.${format ?? "webp"}`;
33752
- const dl = saveToDownloads(buf, filename, { sessionId: session_id, type: "screenshot", sourceUrl: page.url() });
34191
+ const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "screenshot", sourceUrl: page.url() });
33753
34192
  result.download_id = dl.id;
33754
34193
  } catch {}
33755
- if (result.base64.length > 50000) {
34194
+ result.estimated_tokens = Math.ceil(result.base64.length / 4);
34195
+ if (result.base64.length > 20000) {
33756
34196
  result.base64_truncated = true;
33757
34197
  result.full_image_path = result.path;
33758
34198
  result.base64 = result.thumbnail_base64 ?? "";
33759
34199
  }
34200
+ logEvent(sid, "screenshot", { path: result.path });
33760
34201
  return json(result);
33761
34202
  } catch (e) {
33762
34203
  return err(e);
33763
34204
  }
33764
34205
  });
33765
34206
  server.tool("browser_pdf", "Generate a PDF of the current page", {
33766
- session_id: exports_external.string(),
34207
+ session_id: exports_external.string().optional(),
33767
34208
  format: exports_external.enum(["A4", "Letter", "A3", "A5"]).optional().default("A4"),
33768
34209
  landscape: exports_external.boolean().optional().default(false),
33769
34210
  print_background: exports_external.boolean().optional().default(true)
33770
34211
  }, async ({ session_id, format, landscape, print_background }) => {
33771
34212
  try {
33772
- const page = getSessionPage(session_id);
34213
+ const sid = resolveSessionId(session_id);
34214
+ const page = getSessionPage(sid);
33773
34215
  const result = await generatePDF(page, { format, landscape, printBackground: print_background });
33774
34216
  try {
33775
34217
  const buf = Buffer.from(result.base64, "base64");
33776
34218
  const filename = result.path.split("/").pop() ?? "document.pdf";
33777
- const dl = saveToDownloads(buf, filename, { sessionId: session_id, type: "pdf", sourceUrl: page.url() });
34219
+ const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "pdf", sourceUrl: page.url() });
33778
34220
  result.download_id = dl.id;
33779
34221
  } catch {}
33780
34222
  return json(result);
@@ -33782,25 +34224,27 @@ var init_mcp = __esm(async () => {
33782
34224
  return err(e);
33783
34225
  }
33784
34226
  });
33785
- server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string(), script: exports_external.string() }, async ({ session_id, script }) => {
34227
+ server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string().optional(), script: exports_external.string() }, async ({ session_id, script }) => {
33786
34228
  try {
33787
- const page = getSessionPage(session_id);
34229
+ const sid = resolveSessionId(session_id);
34230
+ const page = getSessionPage(sid);
33788
34231
  const result = await page.evaluate(script);
33789
34232
  return json({ result });
33790
34233
  } catch (e) {
33791
- return err(e);
34234
+ return errWithScreenshot(e, session_id);
33792
34235
  }
33793
34236
  });
33794
- server.tool("browser_cookies_get", "Get cookies from the current session", { session_id: exports_external.string(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
34237
+ server.tool("browser_cookies_get", "Get cookies from the current session", { session_id: exports_external.string().optional(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
33795
34238
  try {
33796
- const page = getSessionPage(session_id);
34239
+ const sid = resolveSessionId(session_id);
34240
+ const page = getSessionPage(sid);
33797
34241
  return json({ cookies: await getCookies(page, { name, domain }) });
33798
34242
  } catch (e) {
33799
34243
  return err(e);
33800
34244
  }
33801
34245
  });
33802
34246
  server.tool("browser_cookies_set", "Set a cookie in the current session", {
33803
- session_id: exports_external.string(),
34247
+ session_id: exports_external.string().optional(),
33804
34248
  name: exports_external.string(),
33805
34249
  value: exports_external.string(),
33806
34250
  domain: exports_external.string().optional(),
@@ -33810,7 +34254,8 @@ var init_mcp = __esm(async () => {
33810
34254
  secure: exports_external.boolean().optional().default(false)
33811
34255
  }, async ({ session_id, name, value, domain, path, expires, http_only, secure }) => {
33812
34256
  try {
33813
- const page = getSessionPage(session_id);
34257
+ const sid = resolveSessionId(session_id);
34258
+ const page = getSessionPage(sid);
33814
34259
  await setCookie(page, {
33815
34260
  name,
33816
34261
  value,
@@ -33826,27 +34271,30 @@ var init_mcp = __esm(async () => {
33826
34271
  return err(e);
33827
34272
  }
33828
34273
  });
33829
- server.tool("browser_cookies_clear", "Clear cookies from the current session", { session_id: exports_external.string(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
34274
+ server.tool("browser_cookies_clear", "Clear cookies from the current session", { session_id: exports_external.string().optional(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
33830
34275
  try {
33831
- const page = getSessionPage(session_id);
34276
+ const sid = resolveSessionId(session_id);
34277
+ const page = getSessionPage(sid);
33832
34278
  await clearCookies(page, name || domain ? { name, domain } : undefined);
33833
34279
  return json({ cleared: true });
33834
34280
  } catch (e) {
33835
34281
  return err(e);
33836
34282
  }
33837
34283
  });
33838
- server.tool("browser_storage_get", "Get localStorage or sessionStorage values", { session_id: exports_external.string(), key: exports_external.string().optional(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, storage_type }) => {
34284
+ server.tool("browser_storage_get", "Get localStorage or sessionStorage values", { session_id: exports_external.string().optional(), key: exports_external.string().optional(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, storage_type }) => {
33839
34285
  try {
33840
- const page = getSessionPage(session_id);
34286
+ const sid = resolveSessionId(session_id);
34287
+ const page = getSessionPage(sid);
33841
34288
  const value = storage_type === "session" ? await getSessionStorage(page, key) : await getLocalStorage(page, key);
33842
34289
  return json({ value });
33843
34290
  } catch (e) {
33844
34291
  return err(e);
33845
34292
  }
33846
34293
  });
33847
- server.tool("browser_storage_set", "Set a localStorage or sessionStorage value", { session_id: exports_external.string(), key: exports_external.string(), value: exports_external.string(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, value, storage_type }) => {
34294
+ server.tool("browser_storage_set", "Set a localStorage or sessionStorage value", { session_id: exports_external.string().optional(), key: exports_external.string(), value: exports_external.string(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, value, storage_type }) => {
33848
34295
  try {
33849
- const page = getSessionPage(session_id);
34296
+ const sid = resolveSessionId(session_id);
34297
+ const page = getSessionPage(sid);
33850
34298
  if (storage_type === "session") {
33851
34299
  await setSessionStorage(page, key, value);
33852
34300
  } else {
@@ -33857,28 +34305,30 @@ var init_mcp = __esm(async () => {
33857
34305
  return err(e);
33858
34306
  }
33859
34307
  });
33860
- server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
34308
+ server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33861
34309
  try {
33862
- if (!networkLogCleanup.has(session_id)) {
33863
- const page = getSessionPage(session_id);
33864
- const cleanup = enableNetworkLogging(page, session_id);
33865
- networkLogCleanup.set(session_id, cleanup);
34310
+ const sid = resolveSessionId(session_id);
34311
+ if (!networkLogCleanup.has(sid)) {
34312
+ const page = getSessionPage(sid);
34313
+ const cleanup = enableNetworkLogging(page, sid);
34314
+ networkLogCleanup.set(sid, cleanup);
33866
34315
  }
33867
- const log = getNetworkLog(session_id);
34316
+ const log = getNetworkLog(sid);
33868
34317
  return json({ requests: log, count: log.length });
33869
34318
  } catch (e) {
33870
34319
  return err(e);
33871
34320
  }
33872
34321
  });
33873
34322
  server.tool("browser_network_intercept", "Add a network interception rule", {
33874
- session_id: exports_external.string(),
34323
+ session_id: exports_external.string().optional(),
33875
34324
  pattern: exports_external.string(),
33876
34325
  action: exports_external.enum(["block", "modify", "log"]),
33877
34326
  response_status: exports_external.number().optional(),
33878
34327
  response_body: exports_external.string().optional()
33879
34328
  }, async ({ session_id, pattern, action, response_status, response_body }) => {
33880
34329
  try {
33881
- const page = getSessionPage(session_id);
34330
+ const sid = resolveSessionId(session_id);
34331
+ const page = getSessionPage(sid);
33882
34332
  await addInterceptRule(page, {
33883
34333
  pattern,
33884
34334
  action,
@@ -33889,27 +34339,29 @@ var init_mcp = __esm(async () => {
33889
34339
  return err(e);
33890
34340
  }
33891
34341
  });
33892
- server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
34342
+ server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33893
34343
  try {
33894
- const page = getSessionPage(session_id);
34344
+ const sid = resolveSessionId(session_id);
34345
+ const page = getSessionPage(sid);
33895
34346
  const capture = startHAR(page);
33896
- harCaptures.set(session_id, capture);
34347
+ harCaptures.set(sid, capture);
33897
34348
  return json({ started: true });
33898
34349
  } catch (e) {
33899
34350
  return err(e);
33900
34351
  }
33901
34352
  });
33902
- server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string() }, async ({ session_id }) => {
34353
+ server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33903
34354
  try {
33904
- const capture = harCaptures.get(session_id);
34355
+ const sid = resolveSessionId(session_id);
34356
+ const capture = harCaptures.get(sid);
33905
34357
  if (!capture)
33906
34358
  return err(new Error("No active HAR capture for this session"));
33907
34359
  const har = capture.stop();
33908
- harCaptures.delete(session_id);
34360
+ harCaptures.delete(sid);
33909
34361
  let download_id;
33910
34362
  try {
33911
34363
  const harBuf = Buffer.from(JSON.stringify(har, null, 2));
33912
- const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId: session_id, type: "har" });
34364
+ const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId: sid, type: "har" });
33913
34365
  download_id = dl.id;
33914
34366
  } catch {}
33915
34367
  return json({ har, entry_count: har.log.entries.length, download_id });
@@ -33917,32 +34369,35 @@ var init_mcp = __esm(async () => {
33917
34369
  return err(e);
33918
34370
  }
33919
34371
  });
33920
- server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
34372
+ server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
33921
34373
  try {
33922
- const page = getSessionPage(session_id);
34374
+ const sid = resolveSessionId(session_id);
34375
+ const page = getSessionPage(sid);
33923
34376
  const metrics = await getPerformanceMetrics(page);
33924
34377
  return json({ metrics });
33925
34378
  } catch (e) {
33926
34379
  return err(e);
33927
34380
  }
33928
34381
  });
33929
- server.tool("browser_console_log", "Get captured console messages for a session", { session_id: exports_external.string(), level: exports_external.enum(["log", "warn", "error", "debug", "info"]).optional() }, async ({ session_id, level }) => {
34382
+ server.tool("browser_console_log", "Get captured console messages for a session", { session_id: exports_external.string().optional(), level: exports_external.enum(["log", "warn", "error", "debug", "info"]).optional() }, async ({ session_id, level }) => {
33930
34383
  try {
33931
- if (!consoleCaptureCleanup.has(session_id)) {
33932
- const page = getSessionPage(session_id);
33933
- const cleanup = enableConsoleCapture(page, session_id);
33934
- consoleCaptureCleanup.set(session_id, cleanup);
34384
+ const sid = resolveSessionId(session_id);
34385
+ if (!consoleCaptureCleanup.has(sid)) {
34386
+ const page = getSessionPage(sid);
34387
+ const cleanup = enableConsoleCapture(page, sid);
34388
+ consoleCaptureCleanup.set(sid, cleanup);
33935
34389
  }
33936
- const messages = getConsoleLog(session_id, level);
34390
+ const messages = getConsoleLog(sid, level);
33937
34391
  return json({ messages, count: messages.length });
33938
34392
  } catch (e) {
33939
34393
  return err(e);
33940
34394
  }
33941
34395
  });
33942
- server.tool("browser_record_start", "Start recording actions in a session", { session_id: exports_external.string(), name: exports_external.string(), project_id: exports_external.string().optional() }, async ({ session_id, name }) => {
34396
+ server.tool("browser_record_start", "Start recording actions in a session", { session_id: exports_external.string().optional(), name: exports_external.string(), project_id: exports_external.string().optional() }, async ({ session_id, name }) => {
33943
34397
  try {
33944
- const page = getSessionPage(session_id);
33945
- const recording = startRecording(session_id, name, page.url());
34398
+ const sid = resolveSessionId(session_id);
34399
+ const page = getSessionPage(sid);
34400
+ const recording = startRecording(sid, name, page.url());
33946
34401
  return json({ recording_id: recording.id, name: recording.name });
33947
34402
  } catch (e) {
33948
34403
  return err(e);
@@ -33970,9 +34425,10 @@ var init_mcp = __esm(async () => {
33970
34425
  return err(e);
33971
34426
  }
33972
34427
  });
33973
- server.tool("browser_record_replay", "Replay a recorded sequence in a session", { session_id: exports_external.string(), recording_id: exports_external.string() }, async ({ session_id, recording_id }) => {
34428
+ server.tool("browser_record_replay", "Replay a recorded sequence in a session", { session_id: exports_external.string().optional(), recording_id: exports_external.string() }, async ({ session_id, recording_id }) => {
33974
34429
  try {
33975
- const page = getSessionPage(session_id);
34430
+ const sid = resolveSessionId(session_id);
34431
+ const page = getSessionPage(sid);
33976
34432
  const result = await replayRecording(recording_id, page);
33977
34433
  return json(result);
33978
34434
  } catch (e) {
@@ -34010,7 +34466,7 @@ var init_mcp = __esm(async () => {
34010
34466
  server.tool("browser_register_agent", "Register an agent with the browser service", {
34011
34467
  name: exports_external.string(),
34012
34468
  description: exports_external.string().optional(),
34013
- session_id: exports_external.string().optional(),
34469
+ session_id: exports_external.string().optional().optional(),
34014
34470
  project_id: exports_external.string().optional(),
34015
34471
  working_dir: exports_external.string().optional()
34016
34472
  }, async ({ name, description, session_id, project_id, working_dir }) => {
@@ -34051,9 +34507,10 @@ var init_mcp = __esm(async () => {
34051
34507
  return err(e);
34052
34508
  }
34053
34509
  });
34054
- server.tool("browser_scroll_and_screenshot", "Scroll the page and take a screenshot in one call. Saves 3 separate tool calls.", { session_id: exports_external.string(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(500), wait_ms: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount, wait_ms }) => {
34510
+ server.tool("browser_scroll_and_screenshot", "Scroll the page and take a screenshot in one call. Saves 3 separate tool calls.", { session_id: exports_external.string().optional(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(500), wait_ms: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount, wait_ms }) => {
34055
34511
  try {
34056
- const page = getSessionPage(session_id);
34512
+ const sid = resolveSessionId(session_id);
34513
+ const page = getSessionPage(sid);
34057
34514
  await scroll(page, direction, amount);
34058
34515
  await new Promise((r) => setTimeout(r, wait_ms));
34059
34516
  const result = await takeScreenshot(page, { maxWidth: 1280, track: true });
@@ -34068,9 +34525,10 @@ var init_mcp = __esm(async () => {
34068
34525
  return err(e);
34069
34526
  }
34070
34527
  });
34071
- server.tool("browser_wait_for_navigation", "Wait for URL change after a click or action. Returns the new URL and title.", { session_id: exports_external.string(), timeout: exports_external.number().optional().default(30000), url_pattern: exports_external.string().optional() }, async ({ session_id, timeout, url_pattern }) => {
34528
+ server.tool("browser_wait_for_navigation", "Wait for URL change after a click or action. Returns the new URL and title.", { session_id: exports_external.string().optional(), timeout: exports_external.number().optional().default(30000), url_pattern: exports_external.string().optional() }, async ({ session_id, timeout, url_pattern }) => {
34072
34529
  try {
34073
- const page = getSessionPage(session_id);
34530
+ const sid = resolveSessionId(session_id);
34531
+ const page = getSessionPage(sid);
34074
34532
  const start = Date.now();
34075
34533
  if (url_pattern) {
34076
34534
  await page.waitForURL(url_pattern, { timeout });
@@ -34092,16 +34550,63 @@ var init_mcp = __esm(async () => {
34092
34550
  return err(e);
34093
34551
  }
34094
34552
  });
34095
- server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string(), name: exports_external.string() }, async ({ session_id, name }) => {
34553
+ server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
34554
+ try {
34555
+ const sid = resolveSessionId(session_id);
34556
+ return json({ session: renameSession2(sid, name) });
34557
+ } catch (e) {
34558
+ return err(e);
34559
+ }
34560
+ });
34561
+ server.tool("browser_session_lock", "Lock a session so only the specified agent can use it", { session_id: exports_external.string().optional(), agent_id: exports_external.string() }, async ({ session_id, agent_id }) => {
34562
+ try {
34563
+ const sid = resolveSessionId(session_id);
34564
+ const { lockSession: lockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
34565
+ return json({ session: lockSession2(sid, agent_id) });
34566
+ } catch (e) {
34567
+ return err(e);
34568
+ }
34569
+ });
34570
+ server.tool("browser_session_unlock", "Unlock a session", { session_id: exports_external.string().optional(), agent_id: exports_external.string().optional() }, async ({ session_id, agent_id }) => {
34571
+ try {
34572
+ const sid = resolveSessionId(session_id);
34573
+ const { unlockSession: unlockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
34574
+ return json({ session: unlockSession2(sid, agent_id) });
34575
+ } catch (e) {
34576
+ return err(e);
34577
+ }
34578
+ });
34579
+ server.tool("browser_session_transfer", "Transfer session ownership to another agent", { session_id: exports_external.string().optional(), to_agent_id: exports_external.string() }, async ({ session_id, to_agent_id }) => {
34096
34580
  try {
34097
- return json({ session: renameSession2(session_id, name) });
34581
+ const sid = resolveSessionId(session_id);
34582
+ const { transferSession: transferSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
34583
+ return json({ session: transferSession2(sid, to_agent_id) });
34098
34584
  } catch (e) {
34099
34585
  return err(e);
34100
34586
  }
34101
34587
  });
34102
- server.tool("browser_click_text", "Click an element by its visible text content", { session_id: exports_external.string(), text: exports_external.string(), exact: exports_external.boolean().optional().default(false), timeout: exports_external.number().optional() }, async ({ session_id, text, exact, timeout }) => {
34588
+ server.tool("browser_session_tag", "Add a tag to a session for categorization (e.g. qa, scraping, monitoring)", { session_id: exports_external.string().optional(), tag: exports_external.string() }, async ({ session_id, tag }) => {
34103
34589
  try {
34104
- const page = getSessionPage(session_id);
34590
+ const sid = resolveSessionId(session_id);
34591
+ const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
34592
+ return json({ tags: addSessionTag2(sid, tag) });
34593
+ } catch (e) {
34594
+ return err(e);
34595
+ }
34596
+ });
34597
+ server.tool("browser_session_untag", "Remove a tag from a session", { session_id: exports_external.string().optional(), tag: exports_external.string() }, async ({ session_id, tag }) => {
34598
+ try {
34599
+ const sid = resolveSessionId(session_id);
34600
+ const { removeSessionTag: removeSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
34601
+ return json({ tags: removeSessionTag2(sid, tag) });
34602
+ } catch (e) {
34603
+ return err(e);
34604
+ }
34605
+ });
34606
+ server.tool("browser_click_text", "Click an element by its visible text content", { session_id: exports_external.string().optional(), text: exports_external.string(), exact: exports_external.boolean().optional().default(false), timeout: exports_external.number().optional() }, async ({ session_id, text, exact, timeout }) => {
34607
+ try {
34608
+ const sid = resolveSessionId(session_id);
34609
+ const page = getSessionPage(sid);
34105
34610
  await clickText(page, text, { exact, timeout });
34106
34611
  return json({ clicked: text });
34107
34612
  } catch (e) {
@@ -34109,21 +34614,23 @@ var init_mcp = __esm(async () => {
34109
34614
  }
34110
34615
  });
34111
34616
  server.tool("browser_fill_form", "Fill multiple form fields in one call. Fields map: { selector: value }. Handles text, checkboxes, selects.", {
34112
- session_id: exports_external.string(),
34617
+ session_id: exports_external.string().optional(),
34113
34618
  fields: exports_external.record(exports_external.union([exports_external.string(), exports_external.boolean()])),
34114
34619
  submit_selector: exports_external.string().optional()
34115
34620
  }, async ({ session_id, fields, submit_selector }) => {
34116
34621
  try {
34117
- const page = getSessionPage(session_id);
34622
+ const sid = resolveSessionId(session_id);
34623
+ const page = getSessionPage(sid);
34118
34624
  const result = await fillForm(page, fields, submit_selector);
34119
34625
  return json(result);
34120
34626
  } catch (e) {
34121
- return err(e);
34627
+ return errWithScreenshot(e, session_id);
34122
34628
  }
34123
34629
  });
34124
- server.tool("browser_wait_for_text", "Wait until specific text appears on the page", { session_id: exports_external.string(), text: exports_external.string(), timeout: exports_external.number().optional().default(1e4), exact: exports_external.boolean().optional().default(false) }, async ({ session_id, text, timeout, exact }) => {
34630
+ server.tool("browser_wait_for_text", "Wait until specific text appears on the page", { session_id: exports_external.string().optional(), text: exports_external.string(), timeout: exports_external.number().optional().default(1e4), exact: exports_external.boolean().optional().default(false) }, async ({ session_id, text, timeout, exact }) => {
34125
34631
  try {
34126
- const page = getSessionPage(session_id);
34632
+ const sid = resolveSessionId(session_id);
34633
+ const page = getSessionPage(sid);
34127
34634
  const start = Date.now();
34128
34635
  await waitForText(page, text, { timeout, exact });
34129
34636
  return json({ found: true, elapsed_ms: Date.now() - start });
@@ -34131,46 +34638,51 @@ var init_mcp = __esm(async () => {
34131
34638
  return err(e);
34132
34639
  }
34133
34640
  });
34134
- server.tool("browser_element_exists", "Check if a selector exists on the page (no throw, returns boolean)", { session_id: exports_external.string(), selector: exports_external.string(), check_visible: exports_external.boolean().optional().default(false) }, async ({ session_id, selector, check_visible }) => {
34641
+ server.tool("browser_element_exists", "Check if a selector exists on the page (no throw, returns boolean)", { session_id: exports_external.string().optional(), selector: exports_external.string(), check_visible: exports_external.boolean().optional().default(false) }, async ({ session_id, selector, check_visible }) => {
34135
34642
  try {
34136
- const page = getSessionPage(session_id);
34643
+ const sid = resolveSessionId(session_id);
34644
+ const page = getSessionPage(sid);
34137
34645
  return json(await elementExists(page, selector, { visible: check_visible }));
34138
34646
  } catch (e) {
34139
34647
  return err(e);
34140
34648
  }
34141
34649
  });
34142
- server.tool("browser_get_page_info", "Get a full page summary in one call: url, title, meta tags, link/image/form counts, text length", { session_id: exports_external.string() }, async ({ session_id }) => {
34650
+ server.tool("browser_get_page_info", "Get a full page summary in one call: url, title, meta tags, link/image/form counts, text length", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34143
34651
  try {
34144
- const page = getSessionPage(session_id);
34652
+ const sid = resolveSessionId(session_id);
34653
+ const page = getSessionPage(sid);
34145
34654
  const info = await getPageInfo(page);
34146
- const errors2 = getConsoleLog(session_id, "error");
34655
+ const errors2 = getConsoleLog(sid, "error");
34147
34656
  info.has_console_errors = errors2.length > 0;
34148
34657
  return json(info);
34149
34658
  } catch (e) {
34150
34659
  return err(e);
34151
34660
  }
34152
34661
  });
34153
- server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string() }, async ({ session_id }) => {
34662
+ server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34154
34663
  try {
34155
- const errors2 = getConsoleLog(session_id, "error");
34664
+ const sid = resolveSessionId(session_id);
34665
+ const errors2 = getConsoleLog(sid, "error");
34156
34666
  return json({ has_errors: errors2.length > 0, error_count: errors2.length, errors: errors2 });
34157
34667
  } catch (e) {
34158
34668
  return err(e);
34159
34669
  }
34160
34670
  });
34161
- server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
34671
+ server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34162
34672
  try {
34673
+ const sid = resolveSessionId(session_id);
34163
34674
  const { clearConsoleLog: clearConsoleLog2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
34164
- clearConsoleLog2(session_id);
34675
+ clearConsoleLog2(sid);
34165
34676
  return json({ cleared: true });
34166
34677
  } catch (e) {
34167
34678
  return err(e);
34168
34679
  }
34169
34680
  });
34170
34681
  activeWatchHandles2 = new Map;
34171
- server.tool("browser_watch_start", "Start watching a page for DOM changes", { session_id: exports_external.string(), selector: exports_external.string().optional(), interval_ms: exports_external.number().optional().default(500), max_changes: exports_external.number().optional().default(50) }, async ({ session_id, selector, interval_ms, max_changes }) => {
34682
+ server.tool("browser_watch_start", "Start watching a page for DOM changes", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), interval_ms: exports_external.number().optional().default(500), max_changes: exports_external.number().optional().default(50) }, async ({ session_id, selector, interval_ms, max_changes }) => {
34172
34683
  try {
34173
- const page = getSessionPage(session_id);
34684
+ const sid = resolveSessionId(session_id);
34685
+ const page = getSessionPage(sid);
34174
34686
  const handle = watchPage(page, { selector, intervalMs: interval_ms, maxChanges: max_changes });
34175
34687
  activeWatchHandles2.set(handle.id, handle);
34176
34688
  return json({ watch_id: handle.id });
@@ -34197,7 +34709,7 @@ var init_mcp = __esm(async () => {
34197
34709
  });
34198
34710
  server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
34199
34711
  project_id: exports_external.string().optional(),
34200
- session_id: exports_external.string().optional(),
34712
+ session_id: exports_external.string().optional().optional(),
34201
34713
  tag: exports_external.string().optional(),
34202
34714
  is_favorite: exports_external.boolean().optional(),
34203
34715
  date_from: exports_external.string().optional(),
@@ -34285,14 +34797,14 @@ var init_mcp = __esm(async () => {
34285
34797
  return err(e);
34286
34798
  }
34287
34799
  });
34288
- server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34800
+ server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional().optional() }, async ({ session_id }) => {
34289
34801
  try {
34290
34802
  return json({ downloads: listDownloads(session_id), count: listDownloads(session_id).length });
34291
34803
  } catch (e) {
34292
34804
  return err(e);
34293
34805
  }
34294
34806
  });
34295
- server.tool("browser_downloads_get", "Get a downloaded file by id, returning base64 content and metadata", { id: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, session_id }) => {
34807
+ server.tool("browser_downloads_get", "Get a downloaded file by id, returning base64 content and metadata", { id: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, session_id }) => {
34296
34808
  try {
34297
34809
  const file = getDownload(id, session_id);
34298
34810
  if (!file)
@@ -34303,7 +34815,7 @@ var init_mcp = __esm(async () => {
34303
34815
  return err(e);
34304
34816
  }
34305
34817
  });
34306
- server.tool("browser_downloads_delete", "Delete a downloaded file by id", { id: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, session_id }) => {
34818
+ server.tool("browser_downloads_delete", "Delete a downloaded file by id", { id: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, session_id }) => {
34307
34819
  try {
34308
34820
  const deleted = deleteDownload(id, session_id);
34309
34821
  return json({ deleted });
@@ -34318,7 +34830,7 @@ var init_mcp = __esm(async () => {
34318
34830
  return err(e);
34319
34831
  }
34320
34832
  });
34321
- server.tool("browser_downloads_export", "Copy a downloaded file to a target path", { id: exports_external.string(), target_path: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, target_path, session_id }) => {
34833
+ server.tool("browser_downloads_export", "Copy a downloaded file to a target path", { id: exports_external.string(), target_path: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, target_path, session_id }) => {
34322
34834
  try {
34323
34835
  const finalPath = exportToPath(id, target_path, session_id);
34324
34836
  return json({ path: finalPath });
@@ -34343,12 +34855,13 @@ var init_mcp = __esm(async () => {
34343
34855
  return err(e);
34344
34856
  }
34345
34857
  });
34346
- server.tool("browser_snapshot_diff", "Take a new accessibility snapshot and diff it against the last snapshot for this session. Shows added/removed/modified interactive elements.", { session_id: exports_external.string() }, async ({ session_id }) => {
34858
+ server.tool("browser_snapshot_diff", "Take a new accessibility snapshot and diff it against the last snapshot for this session. Shows added/removed/modified interactive elements.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34347
34859
  try {
34348
- const page = getSessionPage(session_id);
34349
- const before = getLastSnapshot(session_id);
34350
- const after = await takeSnapshot(page, session_id);
34351
- setLastSnapshot(session_id, after);
34860
+ const sid = resolveSessionId(session_id);
34861
+ const page = getSessionPage(sid);
34862
+ const before = getLastSnapshot(sid);
34863
+ const after = await takeSnapshot(page, sid);
34864
+ setLastSnapshot(sid, after);
34352
34865
  if (!before) {
34353
34866
  return json({
34354
34867
  message: "No previous snapshot \u2014 returning current snapshot only.",
@@ -34371,12 +34884,13 @@ var init_mcp = __esm(async () => {
34371
34884
  return err(e);
34372
34885
  }
34373
34886
  });
34374
- server.tool("browser_session_stats", "Get session info and estimated token usage (based on network log, console log, and gallery entry sizes).", { session_id: exports_external.string() }, async ({ session_id }) => {
34887
+ server.tool("browser_session_stats", "Get session info and estimated token usage (based on network log, console log, and gallery entry sizes).", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34375
34888
  try {
34376
- const session = getSession2(session_id);
34377
- const networkLog = getNetworkLog(session_id);
34378
- const consoleLog = getConsoleLog(session_id);
34379
- const galleryEntries = listEntries({ sessionId: session_id, limit: 1000 });
34889
+ const sid = resolveSessionId(session_id);
34890
+ const session = getSession2(sid);
34891
+ const networkLog = getNetworkLog(sid);
34892
+ const consoleLog = getConsoleLog(sid);
34893
+ const galleryEntries = listEntries({ sessionId: sid, limit: 1000 });
34380
34894
  let totalChars = 0;
34381
34895
  for (const req of networkLog) {
34382
34896
  totalChars += (req.url?.length ?? 0) + (req.request_headers?.length ?? 0) + (req.response_headers?.length ?? 0) + (req.request_body?.length ?? 0);
@@ -34388,7 +34902,7 @@ var init_mcp = __esm(async () => {
34388
34902
  totalChars += (entry.url?.length ?? 0) + (entry.title?.length ?? 0) + (entry.notes?.length ?? 0) + (entry.tags?.join(",").length ?? 0);
34389
34903
  }
34390
34904
  const estimatedTokens = Math.ceil(totalChars / 4);
34391
- const tokenBudget = getTokenBudget(session_id);
34905
+ const tokenBudget = getTokenBudget(sid);
34392
34906
  return json({
34393
34907
  session,
34394
34908
  network_request_count: networkLog.length,
@@ -34402,52 +34916,57 @@ var init_mcp = __esm(async () => {
34402
34916
  return err(e);
34403
34917
  }
34404
34918
  });
34405
- server.tool("browser_tab_new", "Open a new tab in the session's browser context, optionally navigating to a URL", { session_id: exports_external.string(), url: exports_external.string().optional() }, async ({ session_id, url }) => {
34919
+ server.tool("browser_tab_new", "Open a new tab in the session's browser context, optionally navigating to a URL", { session_id: exports_external.string().optional(), url: exports_external.string().optional() }, async ({ session_id, url }) => {
34406
34920
  try {
34407
- const page = getSessionPage(session_id);
34921
+ const sid = resolveSessionId(session_id);
34922
+ const page = getSessionPage(sid);
34408
34923
  const tab = await newTab(page, url);
34409
34924
  return json(tab);
34410
34925
  } catch (e) {
34411
34926
  return err(e);
34412
34927
  }
34413
34928
  });
34414
- server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string() }, async ({ session_id }) => {
34929
+ server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34415
34930
  try {
34416
- const page = getSessionPage(session_id);
34931
+ const sid = resolveSessionId(session_id);
34932
+ const page = getSessionPage(sid);
34417
34933
  const tabs = await listTabs(page);
34418
34934
  return json({ tabs, count: tabs.length });
34419
34935
  } catch (e) {
34420
34936
  return err(e);
34421
34937
  }
34422
34938
  });
34423
- server.tool("browser_tab_switch", "Switch to a different tab by index. Updates the session's active page.", { session_id: exports_external.string(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
34939
+ server.tool("browser_tab_switch", "Switch to a different tab by index. Updates the session's active page.", { session_id: exports_external.string().optional(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
34424
34940
  try {
34425
- const page = getSessionPage(session_id);
34941
+ const sid = resolveSessionId(session_id);
34942
+ const page = getSessionPage(sid);
34426
34943
  const result = await switchTab(page, tab_id);
34427
- setSessionPage(session_id, result.page);
34944
+ setSessionPage(sid, result.page);
34428
34945
  return json(result.tab);
34429
34946
  } catch (e) {
34430
34947
  return err(e);
34431
34948
  }
34432
34949
  });
34433
- server.tool("browser_tab_close", "Close a tab by index. Cannot close the last tab.", { session_id: exports_external.string(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
34950
+ server.tool("browser_tab_close", "Close a tab by index. Cannot close the last tab.", { session_id: exports_external.string().optional(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
34434
34951
  try {
34435
- const page = getSessionPage(session_id);
34952
+ const sid = resolveSessionId(session_id);
34953
+ const page = getSessionPage(sid);
34436
34954
  const context = page.context();
34437
34955
  const result = await closeTab(page, tab_id);
34438
34956
  const remainingPages = context.pages();
34439
34957
  const newActivePage = remainingPages[result.active_tab.index];
34440
34958
  if (newActivePage) {
34441
- setSessionPage(session_id, newActivePage);
34959
+ setSessionPage(sid, newActivePage);
34442
34960
  }
34443
34961
  return json(result);
34444
34962
  } catch (e) {
34445
34963
  return err(e);
34446
34964
  }
34447
34965
  });
34448
- server.tool("browser_handle_dialog", "Accept or dismiss a pending dialog (alert, confirm, prompt). Handles the oldest pending dialog.", { session_id: exports_external.string(), action: exports_external.enum(["accept", "dismiss"]), prompt_text: exports_external.string().optional() }, async ({ session_id, action, prompt_text }) => {
34966
+ server.tool("browser_handle_dialog", "Accept or dismiss a pending dialog (alert, confirm, prompt). Handles the oldest pending dialog.", { session_id: exports_external.string().optional(), action: exports_external.enum(["accept", "dismiss"]), prompt_text: exports_external.string().optional() }, async ({ session_id, action, prompt_text }) => {
34449
34967
  try {
34450
- const result = await handleDialog(session_id, action, prompt_text);
34968
+ const sid = resolveSessionId(session_id);
34969
+ const result = await handleDialog(sid, action, prompt_text);
34451
34970
  if (!result.handled)
34452
34971
  return err(new Error("No pending dialogs for this session"));
34453
34972
  return json(result);
@@ -34455,28 +34974,31 @@ var init_mcp = __esm(async () => {
34455
34974
  return err(e);
34456
34975
  }
34457
34976
  });
34458
- server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
34977
+ server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34459
34978
  try {
34460
- const dialogs = getDialogs(session_id);
34979
+ const sid = resolveSessionId(session_id);
34980
+ const dialogs = getDialogs(sid);
34461
34981
  return json({ dialogs, count: dialogs.length });
34462
34982
  } catch (e) {
34463
34983
  return err(e);
34464
34984
  }
34465
34985
  });
34466
- server.tool("browser_profile_save", "Save cookies + localStorage from the current session as a named profile", { session_id: exports_external.string(), name: exports_external.string() }, async ({ session_id, name }) => {
34986
+ server.tool("browser_profile_save", "Save cookies + localStorage from the current session as a named profile", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
34467
34987
  try {
34468
- const page = getSessionPage(session_id);
34988
+ const sid = resolveSessionId(session_id);
34989
+ const page = getSessionPage(sid);
34469
34990
  const info = await saveProfile(page, name);
34470
34991
  return json(info);
34471
34992
  } catch (e) {
34472
34993
  return err(e);
34473
34994
  }
34474
34995
  });
34475
- server.tool("browser_profile_load", "Load a saved profile and apply cookies + localStorage to the current session", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
34996
+ server.tool("browser_profile_load", "Load a saved profile and apply cookies + localStorage to the current session", { session_id: exports_external.string().optional().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
34476
34997
  try {
34477
34998
  const profileData = loadProfile(name);
34478
34999
  if (session_id) {
34479
- const page = getSessionPage(session_id);
35000
+ const sid = resolveSessionId(session_id);
35001
+ const page = getSessionPage(sid);
34480
35002
  const applied = await applyProfile(page, profileData);
34481
35003
  return json({ ...applied, profile: name });
34482
35004
  }
@@ -34614,7 +35136,13 @@ var init_mcp = __esm(async () => {
34614
35136
  { tool: "browser_session_close", description: "Close a session" },
34615
35137
  { tool: "browser_session_get_by_name", description: "Get session by name" },
34616
35138
  { tool: "browser_session_rename", description: "Rename a session" },
35139
+ { tool: "browser_session_lock", description: "Lock a session for an agent" },
35140
+ { tool: "browser_session_unlock", description: "Unlock a session" },
35141
+ { tool: "browser_session_transfer", description: "Transfer session to another agent" },
35142
+ { tool: "browser_session_tag", description: "Add a tag to a session" },
35143
+ { tool: "browser_session_untag", description: "Remove a tag from a session" },
34617
35144
  { tool: "browser_session_stats", description: "Get session stats and token usage" },
35145
+ { tool: "browser_session_timeline", description: "Get chronological action log" },
34618
35146
  { tool: "browser_tab_new", description: "Open a new tab" },
34619
35147
  { tool: "browser_tab_list", description: "List all open tabs" },
34620
35148
  { tool: "browser_tab_switch", description: "Switch to a tab by index" },
@@ -34652,18 +35180,19 @@ var init_mcp = __esm(async () => {
34652
35180
  }
34653
35181
  });
34654
35182
  server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
34655
- session_id: exports_external.string(),
35183
+ session_id: exports_external.string().optional(),
34656
35184
  selector: exports_external.string().optional(),
34657
35185
  ref: exports_external.string().optional(),
34658
35186
  screenshot: exports_external.boolean().optional().default(true),
34659
35187
  wait_ms: exports_external.number().optional().default(200)
34660
35188
  }, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
34661
35189
  try {
34662
- const page = getSessionPage(session_id);
35190
+ const sid = resolveSessionId(session_id);
35191
+ const page = getSessionPage(sid);
34663
35192
  let locator;
34664
35193
  if (ref) {
34665
35194
  const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
34666
- locator = getRefLocator2(page, session_id, ref);
35195
+ locator = getRefLocator2(page, sid, ref);
34667
35196
  } else if (selector) {
34668
35197
  locator = page.locator(selector).first();
34669
35198
  } else {
@@ -34688,11 +35217,12 @@ var init_mcp = __esm(async () => {
34688
35217
  return err(e);
34689
35218
  }
34690
35219
  });
34691
- server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
35220
+ server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
34692
35221
  try {
34693
- const page = getSessionPage(session_id);
35222
+ const sid = resolveSessionId(session_id);
35223
+ const page = getSessionPage(sid);
34694
35224
  const info = await getPageInfo(page);
34695
- const errors2 = getConsoleLog(session_id, "error");
35225
+ const errors2 = getConsoleLog(sid, "error");
34696
35226
  info.has_console_errors = errors2.length > 0;
34697
35227
  let perf = {};
34698
35228
  try {
@@ -34706,8 +35236,8 @@ var init_mcp = __esm(async () => {
34706
35236
  let snapshot_refs = "";
34707
35237
  let interactive_count = 0;
34708
35238
  try {
34709
- const snap = await takeSnapshot(page, session_id);
34710
- setLastSnapshot(session_id, snap);
35239
+ const snap = await takeSnapshot(page, sid);
35240
+ setLastSnapshot(sid, snap);
34711
35241
  interactive_count = snap.interactive_count;
34712
35242
  snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
34713
35243
  } catch {}
@@ -34716,9 +35246,10 @@ var init_mcp = __esm(async () => {
34716
35246
  return err(e);
34717
35247
  }
34718
35248
  });
34719
- server.tool("browser_secrets_login", "Login to a service using credentials from open-secrets vault or ~/.secrets. One call replaces 10+ tool calls.", { session_id: exports_external.string(), service: exports_external.string(), login_url: exports_external.string().optional(), save_profile: exports_external.boolean().optional().default(true) }, async ({ session_id, service, login_url, save_profile }) => {
35249
+ server.tool("browser_secrets_login", "Login to a service using credentials from open-secrets vault or ~/.secrets. One call replaces 10+ tool calls.", { session_id: exports_external.string().optional(), service: exports_external.string(), login_url: exports_external.string().optional(), save_profile: exports_external.boolean().optional().default(true) }, async ({ session_id, service, login_url, save_profile }) => {
34720
35250
  try {
34721
- const page = getSessionPage(session_id);
35251
+ const sid = resolveSessionId(session_id);
35252
+ const page = getSessionPage(sid);
34722
35253
  const { getCredentials: getCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
34723
35254
  const creds = await getCredentials2(service);
34724
35255
  if (!creds)
@@ -34732,9 +35263,10 @@ var init_mcp = __esm(async () => {
34732
35263
  return err(e);
34733
35264
  }
34734
35265
  });
34735
- server.tool("browser_remember", "Store page facts in open-mementos for future recall. Agents skip re-scraping on repeat visits.", { session_id: exports_external.string(), facts: exports_external.record(exports_external.unknown()), tags: exports_external.array(exports_external.string()).optional() }, async ({ session_id, facts, tags }) => {
35266
+ server.tool("browser_remember", "Store page facts in open-mementos for future recall. Agents skip re-scraping on repeat visits.", { session_id: exports_external.string().optional(), facts: exports_external.record(exports_external.unknown()), tags: exports_external.array(exports_external.string()).optional() }, async ({ session_id, facts, tags }) => {
34736
35267
  try {
34737
- const page = getSessionPage(session_id);
35268
+ const sid = resolveSessionId(session_id);
35269
+ const page = getSessionPage(sid);
34738
35270
  const { rememberPage: rememberPage2 } = await Promise.resolve().then(() => (init_page_memory(), exports_page_memory));
34739
35271
  const url = page.url();
34740
35272
  await rememberPage2(url, facts, tags);
@@ -34752,12 +35284,13 @@ var init_mcp = __esm(async () => {
34752
35284
  return err(e);
34753
35285
  }
34754
35286
  });
34755
- server.tool("browser_session_announce", "Announce to other agents via open-conversations what this session is browsing.", { session_id: exports_external.string(), message: exports_external.string().optional() }, async ({ session_id, message }) => {
35287
+ server.tool("browser_session_announce", "Announce to other agents via open-conversations what this session is browsing.", { session_id: exports_external.string().optional(), message: exports_external.string().optional() }, async ({ session_id, message }) => {
34756
35288
  try {
34757
- const page = getSessionPage(session_id);
35289
+ const sid = resolveSessionId(session_id);
35290
+ const page = getSessionPage(sid);
34758
35291
  const { announceNavigation: announceNavigation2 } = await Promise.resolve().then(() => (init_coordination(), exports_coordination));
34759
35292
  const url = page.url();
34760
- await announceNavigation2(url, session_id);
35293
+ await announceNavigation2(url, sid);
34761
35294
  return json({ announced: true, url, message });
34762
35295
  } catch (e) {
34763
35296
  return err(e);
@@ -34797,9 +35330,10 @@ var init_mcp = __esm(async () => {
34797
35330
  return err(e);
34798
35331
  }
34799
35332
  });
34800
- server.tool("browser_skill_run", "Run a pre-built browser skill (login, extract-pricing, extract-nav-links, monitor-price, get-metadata). One call replaces 5\u201315 tool calls.", { session_id: exports_external.string(), skill: exports_external.string(), params: exports_external.record(exports_external.unknown()).optional().default({}) }, async ({ session_id, skill, params }) => {
35333
+ server.tool("browser_skill_run", "Run a pre-built browser skill (login, extract-pricing, extract-nav-links, monitor-price, get-metadata). One call replaces 5\u201315 tool calls.", { session_id: exports_external.string().optional(), skill: exports_external.string(), params: exports_external.record(exports_external.unknown()).optional().default({}) }, async ({ session_id, skill, params }) => {
34801
35334
  try {
34802
- const page = getSessionPage(session_id);
35335
+ const sid = resolveSessionId(session_id);
35336
+ const page = getSessionPage(sid);
34803
35337
  const { runBrowserSkill: runBrowserSkill2 } = await Promise.resolve().then(() => (init_skills_runner(), exports_skills_runner));
34804
35338
  return json(await runBrowserSkill2(skill, params, page));
34805
35339
  } catch (e) {
@@ -34815,7 +35349,7 @@ var init_mcp = __esm(async () => {
34815
35349
  }
34816
35350
  });
34817
35351
  server.tool("browser_batch", "Execute multiple browser actions in one call. Returns final snapshot. Eliminates 80% of round trips for multi-step flows.", {
34818
- session_id: exports_external.string(),
35352
+ session_id: exports_external.string().optional(),
34819
35353
  actions: exports_external.array(exports_external.object({
34820
35354
  tool: exports_external.string(),
34821
35355
  args: exports_external.record(exports_external.unknown()).optional().default({})
@@ -34823,12 +35357,13 @@ var init_mcp = __esm(async () => {
34823
35357
  }, async ({ session_id, actions }) => {
34824
35358
  try {
34825
35359
  const results = [];
34826
- const page = getSessionPage(session_id);
35360
+ const sid = resolveSessionId(session_id);
35361
+ const page = getSessionPage(sid);
34827
35362
  const t0 = Date.now();
34828
35363
  for (const action of actions) {
34829
35364
  try {
34830
35365
  const toolName = action.tool.replace(/^browser_/, "");
34831
- const args = { session_id, ...action.args };
35366
+ const args = { session_id: sid, ...action.args };
34832
35367
  switch (toolName) {
34833
35368
  case "navigate":
34834
35369
  await navigate(page, action.args.url);
@@ -34837,7 +35372,7 @@ var init_mcp = __esm(async () => {
34837
35372
  case "click":
34838
35373
  if (args.ref) {
34839
35374
  const { clickRef: clickRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
34840
- await clickRef2(page, session_id, args.ref);
35375
+ await clickRef2(page, sid, args.ref);
34841
35376
  } else if (args.selector)
34842
35377
  await page.click(args.selector);
34843
35378
  results.push({ tool: action.tool, success: true });
@@ -34845,7 +35380,7 @@ var init_mcp = __esm(async () => {
34845
35380
  case "type":
34846
35381
  if (args.ref && args.text) {
34847
35382
  const { typeRef: typeRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
34848
- await typeRef2(page, session_id, args.ref, args.text);
35383
+ await typeRef2(page, sid, args.ref, args.text);
34849
35384
  } else if (args.selector && args.text)
34850
35385
  await page.fill(args.selector, args.text);
34851
35386
  results.push({ tool: action.tool, success: true });
@@ -34885,7 +35420,7 @@ var init_mcp = __esm(async () => {
34885
35420
  }
34886
35421
  let final_snapshot = {};
34887
35422
  try {
34888
- const snap = await takeSnapshot(page, session_id);
35423
+ const snap = await takeSnapshot(page, sid);
34889
35424
  final_snapshot = {
34890
35425
  refs: Object.fromEntries(Object.entries(snap.refs).slice(0, 20)),
34891
35426
  interactive_count: snap.interactive_count
@@ -34982,18 +35517,20 @@ var init_mcp = __esm(async () => {
34982
35517
  return err(e);
34983
35518
  }
34984
35519
  });
34985
- server.tool("browser_task", "Execute a natural language browser task autonomously using Claude Haiku. Returns result + steps taken.", { session_id: exports_external.string(), task: exports_external.string(), max_steps: exports_external.number().optional().default(10), model: exports_external.string().optional() }, async ({ session_id, task, max_steps, model }) => {
35520
+ server.tool("browser_task", "Execute a natural language browser task autonomously using Claude Haiku. Returns result + steps taken.", { session_id: exports_external.string().optional(), task: exports_external.string(), max_steps: exports_external.number().optional().default(10), model: exports_external.string().optional() }, async ({ session_id, task, max_steps, model }) => {
34986
35521
  try {
34987
- const page = getSessionPage(session_id);
35522
+ const sid = resolveSessionId(session_id);
35523
+ const page = getSessionPage(sid);
34988
35524
  const { executeBrowserTask: executeBrowserTask2 } = await Promise.resolve().then(() => (init_ai_task(), exports_ai_task));
34989
- return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId: session_id }));
35525
+ return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId: sid }));
34990
35526
  } catch (e) {
34991
35527
  return err(e);
34992
35528
  }
34993
35529
  });
34994
- server.tool("browser_assert", `Assert page conditions in one call. Conditions: 'url contains X', 'text:"Y" is visible', 'element:"#id" exists', 'count:"a" > 10', 'title contains Z'. Chain with AND.`, { session_id: exports_external.string(), condition: exports_external.string() }, async ({ session_id, condition }) => {
35530
+ server.tool("browser_assert", `Assert page conditions in one call. Conditions: 'url contains X', 'text:"Y" is visible', 'element:"#id" exists', 'count:"a" > 10', 'title contains Z'. Chain with AND.`, { session_id: exports_external.string().optional(), condition: exports_external.string() }, async ({ session_id, condition }) => {
34995
35531
  try {
34996
- const page = getSessionPage(session_id);
35532
+ const sid = resolveSessionId(session_id);
35533
+ const page = getSessionPage(sid);
34997
35534
  const checks = [];
34998
35535
  let passed = true;
34999
35536
  for (const part of condition.split(/\s+AND\s+/i)) {
@@ -35432,26 +35969,66 @@ import chalk from "chalk";
35432
35969
  var pkg = JSON.parse(readFileSync9(join15(import.meta.dir, "../../package.json"), "utf8"));
35433
35970
  var program2 = new Command;
35434
35971
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
35435
- program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headless", "Run in headless mode (default: true)", true).action(async (url, opts) => {
35436
- const { session, page } = await createSession2({ engine: opts.engine, headless: true });
35437
- console.log(chalk.gray(`Session: ${session.id} (${session.engine})`));
35972
+ program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
35973
+ const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
35438
35974
  await navigate(page, url);
35439
35975
  const title = await page.title();
35440
- console.log(chalk.green(`\u2713 Navigated to: ${url}`));
35441
- console.log(chalk.blue(` Title: ${title}`));
35976
+ let screenshotPath;
35442
35977
  if (opts.screenshot) {
35443
35978
  const result = await takeScreenshot(page);
35444
- console.log(chalk.blue(` Screenshot: ${result.path}`));
35979
+ screenshotPath = result.path;
35445
35980
  }
35981
+ let text;
35446
35982
  if (opts.extract) {
35447
- const text = await getText(page);
35448
- console.log(chalk.white(`
35983
+ text = await getText(page);
35984
+ }
35985
+ if (opts.json) {
35986
+ const output = { session_id: session.id, engine: session.engine, url, title };
35987
+ if (screenshotPath)
35988
+ output.screenshot = screenshotPath;
35989
+ if (text)
35990
+ output.text = text.slice(0, 500);
35991
+ console.log(JSON.stringify(output, null, 2));
35992
+ } else {
35993
+ console.log(chalk.gray(`Session: ${session.id} (${session.engine})`));
35994
+ console.log(chalk.green(`\u2713 Navigated to: ${url}`));
35995
+ console.log(chalk.blue(` Title: ${title}`));
35996
+ if (screenshotPath)
35997
+ console.log(chalk.blue(` Screenshot: ${screenshotPath}`));
35998
+ if (text)
35999
+ console.log(chalk.white(`
35449
36000
  ${text.slice(0, 500)}...`));
35450
36001
  }
35451
36002
  await closeSession2(session.id);
35452
36003
  });
35453
- program2.command("screenshot <url>").description("Navigate to a URL and take a screenshot").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector for element screenshot").option("--full-page", "Capture full page").option("--format <format>", "Image format: png|jpeg|webp", "png").action(async (url, opts) => {
35454
- const { session, page } = await createSession2({ engine: opts.engine, headless: true });
36004
+ program2.command("check <url>").description("One-liner page health check: navigate, screenshot, extract info, check errors").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
36005
+ const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
36006
+ await navigate(page, url);
36007
+ const title = await page.title();
36008
+ const currentUrl = page.url();
36009
+ const text = await getText(page);
36010
+ const links = await getLinks(page);
36011
+ const result = await takeScreenshot(page);
36012
+ const summary = {
36013
+ url: currentUrl,
36014
+ title,
36015
+ text_length: text.length,
36016
+ links_count: links.length,
36017
+ screenshot: result.path,
36018
+ screenshot_size_kb: +(result.size_bytes / 1024).toFixed(1)
36019
+ };
36020
+ if (opts.json) {
36021
+ console.log(JSON.stringify(summary, null, 2));
36022
+ } else {
36023
+ console.log(chalk.green(`\u2713 ${title}`));
36024
+ console.log(chalk.blue(` URL: ${currentUrl}`));
36025
+ console.log(chalk.gray(` Text: ${text.length} chars, Links: ${links.length}`));
36026
+ console.log(chalk.gray(` Screenshot: ${result.path} (${summary.screenshot_size_kb} KB)`));
36027
+ }
36028
+ await closeSession2(session.id);
36029
+ });
36030
+ program2.command("screenshot <url>").description("Navigate to a URL and take a screenshot").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector for element screenshot").option("--full-page", "Capture full page").option("--format <format>", "Image format: png|jpeg|webp", "png").option("--headed", "Run in headed (visible) mode").action(async (url, opts) => {
36031
+ const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
35455
36032
  await navigate(page, url);
35456
36033
  const result = await takeScreenshot(page, {
35457
36034
  selector: opts.selector,
@@ -35462,11 +36039,13 @@ program2.command("screenshot <url>").description("Navigate to a URL and take a s
35462
36039
  console.log(chalk.gray(` Size: ${(result.size_bytes / 1024).toFixed(1)} KB`));
35463
36040
  await closeSession2(session.id);
35464
36041
  });
35465
- program2.command("extract <url>").description("Extract content from a URL").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector").option("--format <format>", "Format: text|html|links|table|structured", "text").action(async (url, opts) => {
35466
- const { session, page } = await createSession2({ engine: opts.engine, headless: true });
36042
+ program2.command("extract <url>").description("Extract content from a URL").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector").option("--format <format>", "Format: text|html|links|table|structured", "text").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
36043
+ const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
35467
36044
  await navigate(page, url);
35468
36045
  const result = await extract(page, { format: opts.format, selector: opts.selector });
35469
- if (opts.format === "links" && result.links) {
36046
+ if (opts.json) {
36047
+ console.log(JSON.stringify(result, null, 2));
36048
+ } else if (opts.format === "links" && result.links) {
35470
36049
  result.links.forEach((l) => console.log(l));
35471
36050
  } else if (opts.format === "table" && result.table) {
35472
36051
  result.table.forEach((row) => console.log(row.join("\t")));
@@ -35475,8 +36054,8 @@ program2.command("extract <url>").description("Extract content from a URL").opti
35475
36054
  }
35476
36055
  await closeSession2(session.id);
35477
36056
  });
35478
- program2.command("eval <url> <script>").description("Run JavaScript in a page context").option("--engine <engine>", "Browser engine", "auto").action(async (url, script, opts) => {
35479
- const { session, page } = await createSession2({ engine: opts.engine, headless: true });
36057
+ program2.command("eval <url> <script>").description("Run JavaScript in a page context").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (url, script, opts) => {
36058
+ const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
35480
36059
  await navigate(page, url);
35481
36060
  const result = await page.evaluate(script);
35482
36061
  console.log(JSON.stringify(result, null, 2));
@@ -35502,14 +36081,16 @@ ${result.errors.length} errors:`));
35502
36081
  }
35503
36082
  });
35504
36083
  var sessionCmd = program2.command("session").description("Manage browser sessions");
35505
- sessionCmd.command("create").description("Create a new browser session").option("--engine <engine>", "Browser engine", "auto").option("--url <url>", "Start URL").action(async (opts) => {
35506
- const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url });
36084
+ sessionCmd.command("create").description("Create a new browser session").option("--engine <engine>", "Browser engine", "auto").option("--url <url>", "Start URL").option("--headed", "Run in headed (visible) mode").action(async (opts) => {
36085
+ const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
35507
36086
  console.log(chalk.green(`\u2713 Session created`));
35508
36087
  console.log(JSON.stringify(session, null, 2));
35509
36088
  });
35510
- sessionCmd.command("list").description("List all sessions").option("--status <status>", "Filter by status").action((opts) => {
36089
+ sessionCmd.command("list").description("List all sessions").option("--status <status>", "Filter by status").option("--json", "Output as JSON").action((opts) => {
35511
36090
  const sessions = listSessions2(opts.status ? { status: opts.status } : undefined);
35512
- if (sessions.length === 0) {
36091
+ if (opts.json) {
36092
+ console.log(JSON.stringify(sessions, null, 2));
36093
+ } else if (sessions.length === 0) {
35513
36094
  console.log(chalk.gray("No sessions found"));
35514
36095
  } else {
35515
36096
  sessions.forEach((s) => console.log(`${s.id} [${s.status}] ${s.engine} ${s.start_url ?? ""}`));
@@ -35520,8 +36101,8 @@ sessionCmd.command("close <id>").description("Close a session").action(async (id
35520
36101
  console.log(chalk.green(`\u2713 Session closed: ${id}`));
35521
36102
  });
35522
36103
  var recordCmd = program2.command("record").description("Manage action recordings");
35523
- recordCmd.command("start <name>").description("Start recording actions in a new session").option("--url <url>", "Start URL").option("--engine <engine>", "Browser engine", "auto").action(async (name, opts) => {
35524
- const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url });
36104
+ recordCmd.command("start <name>").description("Start recording actions in a new session").option("--url <url>", "Start URL").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (name, opts) => {
36105
+ const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
35525
36106
  const recording = startRecording(session.id, name, opts.url);
35526
36107
  console.log(chalk.green(`\u2713 Recording started`));
35527
36108
  console.log(` Recording ID: ${recording.id}`);
@@ -35532,8 +36113,8 @@ recordCmd.command("stop <recording_id>").description("Stop an active recording")
35532
36113
  console.log(chalk.green(`\u2713 Recording stopped: ${recording.name}`));
35533
36114
  console.log(` Steps: ${recording.steps.length}`);
35534
36115
  });
35535
- recordCmd.command("replay <recording_id>").description("Replay a recording in a new session").option("--url <url>", "Override start URL").option("--engine <engine>", "Browser engine", "auto").action(async (id, opts) => {
35536
- const { session, page } = await createSession2({ engine: opts.engine, startUrl: opts.url });
36116
+ recordCmd.command("replay <recording_id>").description("Replay a recording in a new session").option("--url <url>", "Override start URL").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (id, opts) => {
36117
+ const { session, page } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
35537
36118
  const result = await replayRecording(id, page);
35538
36119
  console.log(result.success ? chalk.green("\u2713 Replay complete") : chalk.red("\u2717 Replay had errors"));
35539
36120
  console.log(` Steps: ${result.steps_executed} executed, ${result.steps_failed} failed`);