@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 +857 -276
- package/dist/db/sessions.d.ts +15 -0
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/db/timeline.d.ts +11 -0
- package/dist/db/timeline.d.ts.map +1 -0
- package/dist/engines/playwright.d.ts +1 -1
- package/dist/engines/playwright.d.ts.map +1 -1
- package/dist/index.js +117 -12
- package/dist/lib/session.d.ts +7 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/mcp/index.js +946 -397
- package/dist/server/index.js +98 -14
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/mcp/index.js
CHANGED
|
@@ -275,6 +275,38 @@ function runMigrations(db) {
|
|
|
275
275
|
CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
|
|
276
276
|
CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
|
|
277
277
|
`
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
version: 3,
|
|
281
|
+
sql: `
|
|
282
|
+
-- Session lock/claim for multi-agent ownership
|
|
283
|
+
ALTER TABLE sessions ADD COLUMN locked_by TEXT;
|
|
284
|
+
ALTER TABLE sessions ADD COLUMN locked_at TEXT;
|
|
285
|
+
`
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
version: 4,
|
|
289
|
+
sql: `
|
|
290
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
291
|
+
id TEXT PRIMARY KEY,
|
|
292
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
293
|
+
event_type TEXT NOT NULL,
|
|
294
|
+
details TEXT DEFAULT '{}',
|
|
295
|
+
timestamp TEXT DEFAULT (datetime('now'))
|
|
296
|
+
);
|
|
297
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id, timestamp);
|
|
298
|
+
`
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
version: 5,
|
|
302
|
+
sql: `
|
|
303
|
+
CREATE TABLE IF NOT EXISTS session_tags (
|
|
304
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
305
|
+
tag TEXT NOT NULL,
|
|
306
|
+
PRIMARY KEY (session_id, tag)
|
|
307
|
+
);
|
|
308
|
+
CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
|
|
309
|
+
`
|
|
278
310
|
}
|
|
279
311
|
];
|
|
280
312
|
for (const m of migrations) {
|
|
@@ -290,6 +322,28 @@ var _db = null, _dbPath = null;
|
|
|
290
322
|
var init_schema = () => {};
|
|
291
323
|
|
|
292
324
|
// src/db/sessions.ts
|
|
325
|
+
var exports_sessions = {};
|
|
326
|
+
__export(exports_sessions, {
|
|
327
|
+
updateSessionStatus: () => updateSessionStatus,
|
|
328
|
+
unlockSession: () => unlockSession,
|
|
329
|
+
transferSession: () => transferSession,
|
|
330
|
+
renameSession: () => renameSession,
|
|
331
|
+
removeSessionTag: () => removeSessionTag,
|
|
332
|
+
lockSession: () => lockSession,
|
|
333
|
+
listSessionsByTag: () => listSessionsByTag,
|
|
334
|
+
listSessions: () => listSessions,
|
|
335
|
+
isSessionLocked: () => isSessionLocked,
|
|
336
|
+
getSessionTags: () => getSessionTags,
|
|
337
|
+
getSessionByName: () => getSessionByName,
|
|
338
|
+
getSession: () => getSession,
|
|
339
|
+
getDefaultActiveSession: () => getDefaultActiveSession,
|
|
340
|
+
getActiveSessionForAgent: () => getActiveSessionForAgent,
|
|
341
|
+
deleteSession: () => deleteSession,
|
|
342
|
+
createSession: () => createSession,
|
|
343
|
+
countActiveSessions: () => countActiveSessions,
|
|
344
|
+
closeSession: () => closeSession,
|
|
345
|
+
addSessionTag: () => addSessionTag
|
|
346
|
+
});
|
|
293
347
|
import { randomUUID } from "crypto";
|
|
294
348
|
function createSession(data) {
|
|
295
349
|
const db = getDatabase();
|
|
@@ -342,8 +396,81 @@ function updateSessionStatus(id, status) {
|
|
|
342
396
|
return getSession(id);
|
|
343
397
|
}
|
|
344
398
|
function closeSession(id) {
|
|
399
|
+
const db = getDatabase();
|
|
400
|
+
db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
|
|
345
401
|
return updateSessionStatus(id, "closed");
|
|
346
402
|
}
|
|
403
|
+
function lockSession(id, agentId) {
|
|
404
|
+
const db = getDatabase();
|
|
405
|
+
const session = getSession(id);
|
|
406
|
+
if (session.status !== "active")
|
|
407
|
+
throw new SessionNotFoundError(id);
|
|
408
|
+
const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
|
|
409
|
+
if (row?.locked_by && row.locked_by !== agentId) {
|
|
410
|
+
throw new Error(`Session locked by agent ${row.locked_by}`);
|
|
411
|
+
}
|
|
412
|
+
db.prepare("UPDATE sessions SET locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(agentId, id);
|
|
413
|
+
return getSession(id);
|
|
414
|
+
}
|
|
415
|
+
function unlockSession(id, agentId) {
|
|
416
|
+
const db = getDatabase();
|
|
417
|
+
if (agentId) {
|
|
418
|
+
const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
|
|
419
|
+
if (row?.locked_by && row.locked_by !== agentId) {
|
|
420
|
+
throw new Error(`Session locked by agent ${row.locked_by}, not ${agentId}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
|
|
424
|
+
return getSession(id);
|
|
425
|
+
}
|
|
426
|
+
function isSessionLocked(id) {
|
|
427
|
+
const db = getDatabase();
|
|
428
|
+
const row = db.query("SELECT locked_by, locked_at FROM sessions WHERE id = ?").get(id);
|
|
429
|
+
if (!row)
|
|
430
|
+
throw new SessionNotFoundError(id);
|
|
431
|
+
return { locked: !!row.locked_by, locked_by: row.locked_by ?? undefined, locked_at: row.locked_at ?? undefined };
|
|
432
|
+
}
|
|
433
|
+
function transferSession(id, toAgentId) {
|
|
434
|
+
const db = getDatabase();
|
|
435
|
+
db.prepare("UPDATE sessions SET agent_id = ?, locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(toAgentId, toAgentId, id);
|
|
436
|
+
return getSession(id);
|
|
437
|
+
}
|
|
438
|
+
function getActiveSessionForAgent(agentId) {
|
|
439
|
+
const db = getDatabase();
|
|
440
|
+
return db.query("SELECT * FROM sessions WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1").get(agentId) ?? null;
|
|
441
|
+
}
|
|
442
|
+
function getDefaultActiveSession() {
|
|
443
|
+
const db = getDatabase();
|
|
444
|
+
const rows = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY created_at DESC LIMIT 2").all();
|
|
445
|
+
return rows.length === 1 ? rows[0] : null;
|
|
446
|
+
}
|
|
447
|
+
function countActiveSessions() {
|
|
448
|
+
const db = getDatabase();
|
|
449
|
+
const row = db.query("SELECT COUNT(*) as count FROM sessions WHERE status = 'active'").get();
|
|
450
|
+
return row?.count ?? 0;
|
|
451
|
+
}
|
|
452
|
+
function deleteSession(id) {
|
|
453
|
+
const db = getDatabase();
|
|
454
|
+
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
455
|
+
}
|
|
456
|
+
function addSessionTag(id, tag) {
|
|
457
|
+
const db = getDatabase();
|
|
458
|
+
db.prepare("INSERT OR IGNORE INTO session_tags (session_id, tag) VALUES (?, ?)").run(id, tag);
|
|
459
|
+
return getSessionTags(id);
|
|
460
|
+
}
|
|
461
|
+
function removeSessionTag(id, tag) {
|
|
462
|
+
const db = getDatabase();
|
|
463
|
+
db.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(id, tag);
|
|
464
|
+
return getSessionTags(id);
|
|
465
|
+
}
|
|
466
|
+
function getSessionTags(id) {
|
|
467
|
+
const db = getDatabase();
|
|
468
|
+
return db.query("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(id).map((r) => r.tag);
|
|
469
|
+
}
|
|
470
|
+
function listSessionsByTag(tag) {
|
|
471
|
+
const db = getDatabase();
|
|
472
|
+
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);
|
|
473
|
+
}
|
|
347
474
|
var init_sessions = __esm(() => {
|
|
348
475
|
init_schema();
|
|
349
476
|
init_types();
|
|
@@ -369,10 +496,52 @@ async function getPage(browser, options) {
|
|
|
369
496
|
});
|
|
370
497
|
return context.newPage();
|
|
371
498
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
499
|
+
|
|
500
|
+
class BrowserPool {
|
|
501
|
+
pool = [];
|
|
502
|
+
maxSize;
|
|
503
|
+
options;
|
|
504
|
+
constructor(maxSize = 3, options) {
|
|
505
|
+
this.maxSize = maxSize;
|
|
506
|
+
this.options = options;
|
|
507
|
+
}
|
|
508
|
+
async acquire(headless = true) {
|
|
509
|
+
const available = this.pool.find((e) => !e.inUse);
|
|
510
|
+
if (available) {
|
|
511
|
+
available.inUse = true;
|
|
512
|
+
return available.browser;
|
|
513
|
+
}
|
|
514
|
+
if (this.pool.length < this.maxSize) {
|
|
515
|
+
const browser = await launchPlaywright({ ...this.options, headless });
|
|
516
|
+
this.pool.push({ browser, inUse: true, createdAt: Date.now() });
|
|
517
|
+
return browser;
|
|
518
|
+
}
|
|
519
|
+
return new Promise((resolve) => {
|
|
520
|
+
const interval = setInterval(() => {
|
|
521
|
+
const free = this.pool.find((e) => !e.inUse);
|
|
522
|
+
if (free) {
|
|
523
|
+
clearInterval(interval);
|
|
524
|
+
free.inUse = true;
|
|
525
|
+
resolve(free.browser);
|
|
526
|
+
}
|
|
527
|
+
}, 100);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
release(browser) {
|
|
531
|
+
const entry = this.pool.find((e) => e.browser === browser);
|
|
532
|
+
if (entry)
|
|
533
|
+
entry.inUse = false;
|
|
534
|
+
}
|
|
535
|
+
async destroyAll() {
|
|
536
|
+
await Promise.all(this.pool.map((e) => e.browser.close().catch(() => {})));
|
|
537
|
+
this.pool = [];
|
|
538
|
+
}
|
|
539
|
+
get size() {
|
|
540
|
+
return this.pool.length;
|
|
541
|
+
}
|
|
542
|
+
get available() {
|
|
543
|
+
return this.pool.filter((e) => !e.inUse).length;
|
|
544
|
+
}
|
|
376
545
|
}
|
|
377
546
|
var DEFAULT_VIEWPORT;
|
|
378
547
|
var init_playwright = __esm(() => {
|
|
@@ -1256,6 +1425,7 @@ __export(exports_session, {
|
|
|
1256
1425
|
renameSession: () => renameSession2,
|
|
1257
1426
|
listSessions: () => listSessions2,
|
|
1258
1427
|
isBunSession: () => isBunSession,
|
|
1428
|
+
isAutoGallery: () => isAutoGallery,
|
|
1259
1429
|
hasActiveHandle: () => hasActiveHandle,
|
|
1260
1430
|
getTokenBudget: () => getTokenBudget,
|
|
1261
1431
|
getSessionPage: () => getSessionPage,
|
|
@@ -1264,10 +1434,14 @@ __export(exports_session, {
|
|
|
1264
1434
|
getSessionBunView: () => getSessionBunView,
|
|
1265
1435
|
getSessionBrowser: () => getSessionBrowser,
|
|
1266
1436
|
getSession: () => getSession2,
|
|
1437
|
+
getDefaultSession: () => getDefaultSession,
|
|
1267
1438
|
getActiveSessions: () => getActiveSessions,
|
|
1439
|
+
getActiveSessionForAgent: () => getActiveSessionForAgent2,
|
|
1268
1440
|
createSession: () => createSession2,
|
|
1441
|
+
countActiveSessions: () => countActiveSessions2,
|
|
1269
1442
|
closeSession: () => closeSession2,
|
|
1270
|
-
closeAllSessions: () => closeAllSessions
|
|
1443
|
+
closeAllSessions: () => closeAllSessions,
|
|
1444
|
+
browserPool: () => pool
|
|
1271
1445
|
});
|
|
1272
1446
|
function createBunProxy(view) {
|
|
1273
1447
|
return view;
|
|
@@ -1297,11 +1471,7 @@ async function createSession2(opts = {}) {
|
|
|
1297
1471
|
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
1298
1472
|
page = await context.newPage();
|
|
1299
1473
|
} else {
|
|
1300
|
-
browser = await
|
|
1301
|
-
headless: opts.headless ?? true,
|
|
1302
|
-
viewport: opts.viewport,
|
|
1303
|
-
userAgent: opts.userAgent
|
|
1304
|
-
});
|
|
1474
|
+
browser = await pool.acquire(opts.headless ?? true);
|
|
1305
1475
|
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
1306
1476
|
}
|
|
1307
1477
|
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
@@ -1355,7 +1525,7 @@ async function createSession2(opts = {}) {
|
|
|
1355
1525
|
} catch {}
|
|
1356
1526
|
}
|
|
1357
1527
|
}
|
|
1358
|
-
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
1528
|
+
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
1359
1529
|
if (opts.startUrl) {
|
|
1360
1530
|
try {
|
|
1361
1531
|
if (bunView) {
|
|
@@ -1381,6 +1551,7 @@ function getSessionPage(sessionId) {
|
|
|
1381
1551
|
handles.delete(sessionId);
|
|
1382
1552
|
throw new SessionNotFoundError(sessionId);
|
|
1383
1553
|
}
|
|
1554
|
+
handle.lastActivity = Date.now();
|
|
1384
1555
|
return handle.page;
|
|
1385
1556
|
}
|
|
1386
1557
|
function getSessionBunView(sessionId) {
|
|
@@ -1428,10 +1599,8 @@ async function closeSession2(sessionId) {
|
|
|
1428
1599
|
try {
|
|
1429
1600
|
await handle.page.context().close();
|
|
1430
1601
|
} catch {}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
await closeBrowser(handle.browser);
|
|
1434
|
-
} catch {}
|
|
1602
|
+
if (handle.browser)
|
|
1603
|
+
pool.release(handle.browser);
|
|
1435
1604
|
}
|
|
1436
1605
|
handles.delete(sessionId);
|
|
1437
1606
|
}
|
|
@@ -1450,6 +1619,7 @@ async function closeAllSessions() {
|
|
|
1450
1619
|
for (const [id] of handles) {
|
|
1451
1620
|
await closeSession2(id).catch(() => {});
|
|
1452
1621
|
}
|
|
1622
|
+
await pool.destroyAll();
|
|
1453
1623
|
}
|
|
1454
1624
|
function getSessionByName2(name) {
|
|
1455
1625
|
return getSessionByName(name);
|
|
@@ -1461,7 +1631,49 @@ function getTokenBudget(sessionId) {
|
|
|
1461
1631
|
const handle = handles.get(sessionId);
|
|
1462
1632
|
return handle ? handle.tokenBudget : null;
|
|
1463
1633
|
}
|
|
1464
|
-
|
|
1634
|
+
function getActiveSessionForAgent2(agentId) {
|
|
1635
|
+
const session = getActiveSessionForAgent(agentId);
|
|
1636
|
+
if (!session)
|
|
1637
|
+
return null;
|
|
1638
|
+
const handle = handles.get(session.id);
|
|
1639
|
+
if (!handle)
|
|
1640
|
+
return null;
|
|
1641
|
+
try {
|
|
1642
|
+
if (handle.bunView)
|
|
1643
|
+
handle.bunView.url();
|
|
1644
|
+
else
|
|
1645
|
+
handle.page.url();
|
|
1646
|
+
} catch {
|
|
1647
|
+
handles.delete(session.id);
|
|
1648
|
+
return null;
|
|
1649
|
+
}
|
|
1650
|
+
return { session, page: handle.page };
|
|
1651
|
+
}
|
|
1652
|
+
function getDefaultSession() {
|
|
1653
|
+
const session = getDefaultActiveSession();
|
|
1654
|
+
if (!session)
|
|
1655
|
+
return null;
|
|
1656
|
+
const handle = handles.get(session.id);
|
|
1657
|
+
if (!handle)
|
|
1658
|
+
return null;
|
|
1659
|
+
try {
|
|
1660
|
+
if (handle.bunView)
|
|
1661
|
+
handle.bunView.url();
|
|
1662
|
+
else
|
|
1663
|
+
handle.page.url();
|
|
1664
|
+
} catch {
|
|
1665
|
+
handles.delete(session.id);
|
|
1666
|
+
return null;
|
|
1667
|
+
}
|
|
1668
|
+
return { session, page: handle.page };
|
|
1669
|
+
}
|
|
1670
|
+
function isAutoGallery(sessionId) {
|
|
1671
|
+
return handles.get(sessionId)?.autoGallery ?? false;
|
|
1672
|
+
}
|
|
1673
|
+
function countActiveSessions2() {
|
|
1674
|
+
return countActiveSessions();
|
|
1675
|
+
}
|
|
1676
|
+
var handles, pool, SESSION_TTL_MS, ttlInterval;
|
|
1465
1677
|
var init_session = __esm(() => {
|
|
1466
1678
|
init_types();
|
|
1467
1679
|
init_types();
|
|
@@ -1475,6 +1687,20 @@ var init_session = __esm(() => {
|
|
|
1475
1687
|
init_stealth();
|
|
1476
1688
|
init_dialogs();
|
|
1477
1689
|
handles = new Map;
|
|
1690
|
+
pool = new BrowserPool(5);
|
|
1691
|
+
SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
|
|
1692
|
+
ttlInterval = setInterval(async () => {
|
|
1693
|
+
const now = Date.now();
|
|
1694
|
+
for (const [id, handle] of handles) {
|
|
1695
|
+
if (now - handle.lastActivity > SESSION_TTL_MS) {
|
|
1696
|
+
try {
|
|
1697
|
+
await closeSession2(id);
|
|
1698
|
+
} catch {}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}, 60000);
|
|
1702
|
+
if (ttlInterval.unref)
|
|
1703
|
+
ttlInterval.unref();
|
|
1478
1704
|
});
|
|
1479
1705
|
|
|
1480
1706
|
// src/lib/snapshot.ts
|
|
@@ -8674,6 +8900,163 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
8674
8900
|
module.exports = Sharp;
|
|
8675
8901
|
});
|
|
8676
8902
|
|
|
8903
|
+
// src/db/gallery.ts
|
|
8904
|
+
var exports_gallery = {};
|
|
8905
|
+
__export(exports_gallery, {
|
|
8906
|
+
updateEntry: () => updateEntry,
|
|
8907
|
+
untagEntry: () => untagEntry,
|
|
8908
|
+
tagEntry: () => tagEntry,
|
|
8909
|
+
searchEntries: () => searchEntries,
|
|
8910
|
+
listEntries: () => listEntries,
|
|
8911
|
+
getGalleryStats: () => getGalleryStats,
|
|
8912
|
+
getEntry: () => getEntry,
|
|
8913
|
+
favoriteEntry: () => favoriteEntry,
|
|
8914
|
+
deleteEntry: () => deleteEntry,
|
|
8915
|
+
createEntry: () => createEntry
|
|
8916
|
+
});
|
|
8917
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
8918
|
+
function deserialize(row) {
|
|
8919
|
+
return {
|
|
8920
|
+
id: row.id,
|
|
8921
|
+
session_id: row.session_id ?? undefined,
|
|
8922
|
+
project_id: row.project_id ?? undefined,
|
|
8923
|
+
url: row.url ?? undefined,
|
|
8924
|
+
title: row.title ?? undefined,
|
|
8925
|
+
path: row.path,
|
|
8926
|
+
thumbnail_path: row.thumbnail_path ?? undefined,
|
|
8927
|
+
format: row.format ?? undefined,
|
|
8928
|
+
width: row.width ?? undefined,
|
|
8929
|
+
height: row.height ?? undefined,
|
|
8930
|
+
original_size_bytes: row.original_size_bytes ?? undefined,
|
|
8931
|
+
compressed_size_bytes: row.compressed_size_bytes ?? undefined,
|
|
8932
|
+
compression_ratio: row.compression_ratio ?? undefined,
|
|
8933
|
+
tags: JSON.parse(row.tags),
|
|
8934
|
+
notes: row.notes ?? undefined,
|
|
8935
|
+
is_favorite: row.is_favorite === 1,
|
|
8936
|
+
created_at: row.created_at
|
|
8937
|
+
};
|
|
8938
|
+
}
|
|
8939
|
+
function createEntry(data) {
|
|
8940
|
+
const db = getDatabase();
|
|
8941
|
+
const id = randomUUID4();
|
|
8942
|
+
db.prepare(`
|
|
8943
|
+
INSERT INTO gallery_entries
|
|
8944
|
+
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
8945
|
+
width, height, original_size_bytes, compressed_size_bytes, compression_ratio,
|
|
8946
|
+
tags, notes, is_favorite)
|
|
8947
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8948
|
+
`).run(id, data.session_id ?? null, data.project_id ?? null, data.url ?? null, data.title ?? null, data.path, data.thumbnail_path ?? null, data.format ?? null, data.width ?? null, data.height ?? null, data.original_size_bytes ?? null, data.compressed_size_bytes ?? null, data.compression_ratio ?? null, JSON.stringify(data.tags ?? []), data.notes ?? null, data.is_favorite ? 1 : 0);
|
|
8949
|
+
return getEntry(id);
|
|
8950
|
+
}
|
|
8951
|
+
function getEntry(id) {
|
|
8952
|
+
const db = getDatabase();
|
|
8953
|
+
const row = db.query("SELECT * FROM gallery_entries WHERE id = ?").get(id);
|
|
8954
|
+
return row ? deserialize(row) : null;
|
|
8955
|
+
}
|
|
8956
|
+
function listEntries(filter) {
|
|
8957
|
+
const db = getDatabase();
|
|
8958
|
+
const conditions = [];
|
|
8959
|
+
const values = [];
|
|
8960
|
+
if (filter?.projectId) {
|
|
8961
|
+
conditions.push("project_id = ?");
|
|
8962
|
+
values.push(filter.projectId);
|
|
8963
|
+
}
|
|
8964
|
+
if (filter?.sessionId) {
|
|
8965
|
+
conditions.push("session_id = ?");
|
|
8966
|
+
values.push(filter.sessionId);
|
|
8967
|
+
}
|
|
8968
|
+
if (filter?.isFavorite !== undefined) {
|
|
8969
|
+
conditions.push("is_favorite = ?");
|
|
8970
|
+
values.push(filter.isFavorite ? 1 : 0);
|
|
8971
|
+
}
|
|
8972
|
+
if (filter?.dateFrom) {
|
|
8973
|
+
conditions.push("created_at >= ?");
|
|
8974
|
+
values.push(filter.dateFrom);
|
|
8975
|
+
}
|
|
8976
|
+
if (filter?.dateTo) {
|
|
8977
|
+
conditions.push("created_at <= ?");
|
|
8978
|
+
values.push(filter.dateTo);
|
|
8979
|
+
}
|
|
8980
|
+
if (filter?.tag) {
|
|
8981
|
+
conditions.push("tags LIKE ?");
|
|
8982
|
+
values.push(`%"${filter.tag}"%`);
|
|
8983
|
+
}
|
|
8984
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
8985
|
+
const limit = filter?.limit ?? 50;
|
|
8986
|
+
const offset = filter?.offset ?? 0;
|
|
8987
|
+
const rows = db.query(`SELECT * FROM gallery_entries ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...values, limit, offset);
|
|
8988
|
+
return rows.map(deserialize);
|
|
8989
|
+
}
|
|
8990
|
+
function updateEntry(id, data) {
|
|
8991
|
+
const db = getDatabase();
|
|
8992
|
+
const fields = [];
|
|
8993
|
+
const values = [];
|
|
8994
|
+
if (data.notes !== undefined) {
|
|
8995
|
+
fields.push("notes = ?");
|
|
8996
|
+
values.push(data.notes);
|
|
8997
|
+
}
|
|
8998
|
+
if (data.is_favorite !== undefined) {
|
|
8999
|
+
fields.push("is_favorite = ?");
|
|
9000
|
+
values.push(data.is_favorite ? 1 : 0);
|
|
9001
|
+
}
|
|
9002
|
+
if (data.tags !== undefined) {
|
|
9003
|
+
fields.push("tags = ?");
|
|
9004
|
+
values.push(JSON.stringify(data.tags));
|
|
9005
|
+
}
|
|
9006
|
+
if (fields.length === 0)
|
|
9007
|
+
return getEntry(id);
|
|
9008
|
+
values.push(id);
|
|
9009
|
+
db.prepare(`UPDATE gallery_entries SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
9010
|
+
return getEntry(id);
|
|
9011
|
+
}
|
|
9012
|
+
function deleteEntry(id) {
|
|
9013
|
+
const db = getDatabase();
|
|
9014
|
+
db.prepare("DELETE FROM gallery_entries WHERE id = ?").run(id);
|
|
9015
|
+
}
|
|
9016
|
+
function tagEntry(id, tag) {
|
|
9017
|
+
const entry = getEntry(id);
|
|
9018
|
+
if (!entry)
|
|
9019
|
+
return null;
|
|
9020
|
+
const tags = entry.tags.includes(tag) ? entry.tags : [...entry.tags, tag];
|
|
9021
|
+
return updateEntry(id, { tags });
|
|
9022
|
+
}
|
|
9023
|
+
function untagEntry(id, tag) {
|
|
9024
|
+
const entry = getEntry(id);
|
|
9025
|
+
if (!entry)
|
|
9026
|
+
return null;
|
|
9027
|
+
return updateEntry(id, { tags: entry.tags.filter((t) => t !== tag) });
|
|
9028
|
+
}
|
|
9029
|
+
function favoriteEntry(id, value) {
|
|
9030
|
+
return updateEntry(id, { is_favorite: value });
|
|
9031
|
+
}
|
|
9032
|
+
function searchEntries(q, limit = 20) {
|
|
9033
|
+
const db = getDatabase();
|
|
9034
|
+
const like = `%${q}%`;
|
|
9035
|
+
const rows = db.query(`
|
|
9036
|
+
SELECT * FROM gallery_entries
|
|
9037
|
+
WHERE url LIKE ? OR title LIKE ? OR notes LIKE ? OR tags LIKE ?
|
|
9038
|
+
ORDER BY created_at DESC LIMIT ${limit}
|
|
9039
|
+
`).all(like, like, like, like);
|
|
9040
|
+
return rows.map(deserialize);
|
|
9041
|
+
}
|
|
9042
|
+
function getGalleryStats(projectId) {
|
|
9043
|
+
const db = getDatabase();
|
|
9044
|
+
const where = projectId ? "WHERE project_id = ?" : "";
|
|
9045
|
+
const params = projectId ? [projectId] : [];
|
|
9046
|
+
const total = db.query(`SELECT COUNT(*) as count FROM gallery_entries ${where}`).get(...params)?.count ?? 0;
|
|
9047
|
+
const totalSize = db.query(`SELECT COALESCE(SUM(compressed_size_bytes), 0) as total FROM gallery_entries ${where}`).get(...params)?.total ?? 0;
|
|
9048
|
+
const favorites = db.query(`SELECT COUNT(*) as count FROM gallery_entries ${where ? where + " AND" : "WHERE"} is_favorite = 1`).get(...projectId ? [projectId] : [])?.count ?? 0;
|
|
9049
|
+
const formatRows = db.query(`SELECT format, COUNT(*) as count FROM gallery_entries ${where} GROUP BY format`).all(...params);
|
|
9050
|
+
const by_format = {};
|
|
9051
|
+
for (const row of formatRows) {
|
|
9052
|
+
by_format[row.format ?? "unknown"] = row.count;
|
|
9053
|
+
}
|
|
9054
|
+
return { total, total_size_bytes: totalSize, favorites, by_format };
|
|
9055
|
+
}
|
|
9056
|
+
var init_gallery = __esm(() => {
|
|
9057
|
+
init_schema();
|
|
9058
|
+
});
|
|
9059
|
+
|
|
8677
9060
|
// src/lib/profiles.ts
|
|
8678
9061
|
var exports_profiles = {};
|
|
8679
9062
|
__export(exports_profiles, {
|
|
@@ -8984,6 +9367,7 @@ __export(exports_dist, {
|
|
|
8984
9367
|
shortUuid: () => shortUuid,
|
|
8985
9368
|
setFocus: () => setFocus,
|
|
8986
9369
|
setActiveProfile: () => setActiveProfile,
|
|
9370
|
+
setActiveModel: () => setActiveModel,
|
|
8987
9371
|
searchMemories: () => searchMemories,
|
|
8988
9372
|
runCleanup: () => runCleanup,
|
|
8989
9373
|
resolveProjectId: () => resolveProjectId,
|
|
@@ -9034,6 +9418,8 @@ __export(exports_dist, {
|
|
|
9034
9418
|
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
9035
9419
|
getAgent: () => getAgent2,
|
|
9036
9420
|
getActiveProfile: () => getActiveProfile,
|
|
9421
|
+
getActiveModel: () => getActiveModel,
|
|
9422
|
+
gatherTrainingData: () => gatherTrainingData,
|
|
9037
9423
|
focusFilterSQL: () => focusFilterSQL,
|
|
9038
9424
|
findPath: () => findPath,
|
|
9039
9425
|
enforceQuotas: () => enforceQuotas,
|
|
@@ -9050,6 +9436,7 @@ __export(exports_dist, {
|
|
|
9050
9436
|
containsSecrets: () => containsSecrets,
|
|
9051
9437
|
configureAutoMemory: () => configureAutoMemory,
|
|
9052
9438
|
closeDatabase: () => closeDatabase,
|
|
9439
|
+
clearActiveModel: () => clearActiveModel,
|
|
9053
9440
|
cleanExpiredMemories: () => cleanExpiredMemories,
|
|
9054
9441
|
cleanExpiredLocks: () => cleanExpiredLocks,
|
|
9055
9442
|
checkMemoryWriteLock: () => checkMemoryWriteLock,
|
|
@@ -9070,6 +9457,7 @@ __export(exports_dist, {
|
|
|
9070
9457
|
InvalidScopeError: () => InvalidScopeError,
|
|
9071
9458
|
EntityNotFoundError: () => EntityNotFoundError,
|
|
9072
9459
|
DuplicateMemoryError: () => DuplicateMemoryError,
|
|
9460
|
+
DEFAULT_MODEL: () => DEFAULT_MODEL,
|
|
9073
9461
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
9074
9462
|
});
|
|
9075
9463
|
import { Database as Database2 } from "bun:sqlite";
|
|
@@ -9081,6 +9469,9 @@ import { basename as basename2, dirname as dirname2, join as join22, resolve as
|
|
|
9081
9469
|
import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
9082
9470
|
import { homedir as homedir22 } from "os";
|
|
9083
9471
|
import { join as join32 } from "path";
|
|
9472
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync42, readFileSync as readFileSync32, writeFileSync as writeFileSync32 } from "fs";
|
|
9473
|
+
import { homedir as homedir32 } from "os";
|
|
9474
|
+
import { join as join42 } from "path";
|
|
9084
9475
|
function isInMemoryDb(path) {
|
|
9085
9476
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
9086
9477
|
}
|
|
@@ -12114,6 +12505,85 @@ function jaccardSimilarity(a, b) {
|
|
|
12114
12505
|
const union = new Set([...a, ...b]).size;
|
|
12115
12506
|
return intersection / union;
|
|
12116
12507
|
}
|
|
12508
|
+
function memoryToRecallExample(memory) {
|
|
12509
|
+
return {
|
|
12510
|
+
messages: [
|
|
12511
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
12512
|
+
{
|
|
12513
|
+
role: "user",
|
|
12514
|
+
content: `What do you remember about "${memory.key}"?`
|
|
12515
|
+
},
|
|
12516
|
+
{
|
|
12517
|
+
role: "assistant",
|
|
12518
|
+
content: memory.summary ? `${memory.value}
|
|
12519
|
+
|
|
12520
|
+
Summary: ${memory.summary}` : memory.value
|
|
12521
|
+
}
|
|
12522
|
+
]
|
|
12523
|
+
};
|
|
12524
|
+
}
|
|
12525
|
+
function memoryToSaveExample(memory) {
|
|
12526
|
+
const tags = memory.tags ?? [];
|
|
12527
|
+
return {
|
|
12528
|
+
messages: [
|
|
12529
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
12530
|
+
{
|
|
12531
|
+
role: "user",
|
|
12532
|
+
content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
|
|
12533
|
+
},
|
|
12534
|
+
{
|
|
12535
|
+
role: "assistant",
|
|
12536
|
+
content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
|
|
12537
|
+
}
|
|
12538
|
+
]
|
|
12539
|
+
};
|
|
12540
|
+
}
|
|
12541
|
+
function memoryToSearchExample(memories, category) {
|
|
12542
|
+
const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
|
|
12543
|
+
return {
|
|
12544
|
+
messages: [
|
|
12545
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
12546
|
+
{ role: "user", content: `What ${category} memories do you have?` },
|
|
12547
|
+
{
|
|
12548
|
+
role: "assistant",
|
|
12549
|
+
content: matched.length > 0 ? `Here are my ${category} memories:
|
|
12550
|
+
${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
|
|
12551
|
+
`)}` : `I don't have any ${category} memories stored yet.`
|
|
12552
|
+
}
|
|
12553
|
+
]
|
|
12554
|
+
};
|
|
12555
|
+
}
|
|
12556
|
+
function readConfig() {
|
|
12557
|
+
if (!existsSync42(CONFIG_PATH))
|
|
12558
|
+
return {};
|
|
12559
|
+
try {
|
|
12560
|
+
const raw = readFileSync32(CONFIG_PATH, "utf-8");
|
|
12561
|
+
return JSON.parse(raw);
|
|
12562
|
+
} catch {
|
|
12563
|
+
return {};
|
|
12564
|
+
}
|
|
12565
|
+
}
|
|
12566
|
+
function writeConfig(config) {
|
|
12567
|
+
if (!existsSync42(CONFIG_DIR)) {
|
|
12568
|
+
mkdirSync42(CONFIG_DIR, { recursive: true });
|
|
12569
|
+
}
|
|
12570
|
+
writeFileSync32(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
12571
|
+
`, "utf-8");
|
|
12572
|
+
}
|
|
12573
|
+
function getActiveModel() {
|
|
12574
|
+
const config = readConfig();
|
|
12575
|
+
return config.activeModel ?? DEFAULT_MODEL;
|
|
12576
|
+
}
|
|
12577
|
+
function setActiveModel(modelId) {
|
|
12578
|
+
const config = readConfig();
|
|
12579
|
+
config.activeModel = modelId;
|
|
12580
|
+
writeConfig(config);
|
|
12581
|
+
}
|
|
12582
|
+
function clearActiveModel() {
|
|
12583
|
+
const config = readConfig();
|
|
12584
|
+
delete config.activeModel;
|
|
12585
|
+
writeConfig(config);
|
|
12586
|
+
}
|
|
12117
12587
|
var __defProp2, __export2 = (target, all) => {
|
|
12118
12588
|
for (var name in all)
|
|
12119
12589
|
__defProp2(target, name, {
|
|
@@ -12156,7 +12626,27 @@ Return JSON with this exact shape:
|
|
|
12156
12626
|
"relations": [
|
|
12157
12627
|
{ "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" }
|
|
12158
12628
|
]
|
|
12159
|
-
}`, 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
|
|
12629
|
+
}`, 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 = {}) => {
|
|
12630
|
+
const allMemories = listMemories({ status: "active" });
|
|
12631
|
+
const filtered = options.since ? allMemories.filter((m) => new Date(m.created_at) >= options.since) : allMemories;
|
|
12632
|
+
const sorted = filtered.slice().sort((a, b) => b.importance - a.importance);
|
|
12633
|
+
const fetchSet = options.limit ? sorted.slice(0, options.limit * 3) : sorted;
|
|
12634
|
+
const examples = [];
|
|
12635
|
+
for (const memory of fetchSet) {
|
|
12636
|
+
examples.push(memoryToRecallExample(memory));
|
|
12637
|
+
examples.push(memoryToSaveExample(memory));
|
|
12638
|
+
}
|
|
12639
|
+
const categories = [...new Set(fetchSet.map((m) => m.category))];
|
|
12640
|
+
for (const category of categories) {
|
|
12641
|
+
examples.push(memoryToSearchExample(fetchSet, category));
|
|
12642
|
+
}
|
|
12643
|
+
const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
|
|
12644
|
+
return {
|
|
12645
|
+
source: "mementos",
|
|
12646
|
+
examples: finalExamples,
|
|
12647
|
+
count: finalExamples.length
|
|
12648
|
+
};
|
|
12649
|
+
}, DEFAULT_MODEL = "gpt-4o-mini", CONFIG_DIR, CONFIG_PATH;
|
|
12160
12650
|
var init_dist = __esm(() => {
|
|
12161
12651
|
__defProp2 = Object.defineProperty;
|
|
12162
12652
|
exports_database = {};
|
|
@@ -13436,6 +13926,8 @@ Return only a number 0-10.`);
|
|
|
13436
13926
|
keepLonger: true
|
|
13437
13927
|
};
|
|
13438
13928
|
_stats = { checked: 0, skipped: 0, updated: 0 };
|
|
13929
|
+
CONFIG_DIR = join42(homedir32(), ".mementos");
|
|
13930
|
+
CONFIG_PATH = join42(CONFIG_DIR, "config.json");
|
|
13439
13931
|
});
|
|
13440
13932
|
|
|
13441
13933
|
// src/lib/page-memory.ts
|
|
@@ -13596,13 +14088,13 @@ import { homedir as homedir10 } from "os";
|
|
|
13596
14088
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
13597
14089
|
import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
|
|
13598
14090
|
import { join as join33 } from "path";
|
|
13599
|
-
import { homedir as
|
|
14091
|
+
import { homedir as homedir33 } from "os";
|
|
13600
14092
|
import { readFileSync as readFileSync5 } from "fs";
|
|
13601
14093
|
import { join as join23 } from "path";
|
|
13602
14094
|
import { homedir as homedir23 } from "os";
|
|
13603
14095
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
13604
14096
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync33 } from "fs";
|
|
13605
|
-
import { join as
|
|
14097
|
+
import { join as join43, dirname as dirname22 } from "path";
|
|
13606
14098
|
import { homedir as homedir42 } from "os";
|
|
13607
14099
|
function getDbPath2() {
|
|
13608
14100
|
if (process.env.CONVERSATIONS_DB_PATH)
|
|
@@ -13941,7 +14433,7 @@ function parseMessage(row) {
|
|
|
13941
14433
|
function getAttachmentsDir() {
|
|
13942
14434
|
if (process.env.CONVERSATIONS_ATTACHMENTS_DIR)
|
|
13943
14435
|
return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
|
|
13944
|
-
return join33(
|
|
14436
|
+
return join33(homedir33(), ".conversations", "attachments");
|
|
13945
14437
|
}
|
|
13946
14438
|
function guessMimeType(name) {
|
|
13947
14439
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
@@ -17612,7 +18104,7 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
17612
18104
|
"zinc-eagle",
|
|
17613
18105
|
"zone-fox"
|
|
17614
18106
|
];
|
|
17615
|
-
AGENT_ID_FILE =
|
|
18107
|
+
AGENT_ID_FILE = join43(homedir42(), ".conversations", "agent-id");
|
|
17616
18108
|
init_db();
|
|
17617
18109
|
init_db();
|
|
17618
18110
|
CONFLICT_THRESHOLD_SECONDS = 30 * 60;
|
|
@@ -17954,7 +18446,7 @@ __export(exports_dist3, {
|
|
|
17954
18446
|
deleteTemplate: () => deleteTemplate,
|
|
17955
18447
|
deleteTaskList: () => deleteTaskList,
|
|
17956
18448
|
deleteTask: () => deleteTask,
|
|
17957
|
-
deleteSession: () =>
|
|
18449
|
+
deleteSession: () => deleteSession2,
|
|
17958
18450
|
deleteProject: () => deleteProject2,
|
|
17959
18451
|
deletePlan: () => deletePlan,
|
|
17960
18452
|
deleteOrg: () => deleteOrg,
|
|
@@ -18021,11 +18513,11 @@ import { existsSync as existsSync33 } from "fs";
|
|
|
18021
18513
|
import { join as join34 } from "path";
|
|
18022
18514
|
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
18023
18515
|
import { join as join24 } from "path";
|
|
18024
|
-
import { existsSync as
|
|
18025
|
-
import { join as
|
|
18516
|
+
import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
|
|
18517
|
+
import { join as join44 } from "path";
|
|
18026
18518
|
import { existsSync as existsSync52 } from "fs";
|
|
18027
18519
|
import { join as join52 } from "path";
|
|
18028
|
-
import { readFileSync as
|
|
18520
|
+
import { readFileSync as readFileSync33, statSync as statSync22 } from "fs";
|
|
18029
18521
|
import { relative, resolve as resolve22, join as join62 } from "path";
|
|
18030
18522
|
import { execSync as execSync2 } from "child_process";
|
|
18031
18523
|
|
|
@@ -20383,11 +20875,11 @@ function autoReleaseStaleAgents(db2) {
|
|
|
20383
20875
|
const result = d.run("UPDATE agents SET session_id = NULL WHERE status = 'active' AND session_id IS NOT NULL AND last_seen_at < ?", [cutoff]);
|
|
20384
20876
|
return result.changes;
|
|
20385
20877
|
}
|
|
20386
|
-
function getAvailableNamesFromPool(
|
|
20878
|
+
function getAvailableNamesFromPool(pool2, db2) {
|
|
20387
20879
|
autoReleaseStaleAgents(db2);
|
|
20388
20880
|
const cutoff = new Date(Date.now() - getActiveWindowMs()).toISOString();
|
|
20389
20881
|
const activeNames = new Set(db2.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
20390
|
-
return
|
|
20882
|
+
return pool2.filter((name) => !activeNames.has(name.toLowerCase()));
|
|
20391
20883
|
}
|
|
20392
20884
|
function shortUuid2() {
|
|
20393
20885
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -20464,9 +20956,9 @@ function registerAgent5(input, db2) {
|
|
|
20464
20956
|
function isAgentConflict2(result) {
|
|
20465
20957
|
return result.conflict === true;
|
|
20466
20958
|
}
|
|
20467
|
-
function buildConflictError(existing, lastSeenMs,
|
|
20959
|
+
function buildConflictError(existing, lastSeenMs, pool2, d) {
|
|
20468
20960
|
const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
|
|
20469
|
-
const suggestions =
|
|
20961
|
+
const suggestions = pool2 ? getAvailableNamesFromPool(pool2, d) : [];
|
|
20470
20962
|
return {
|
|
20471
20963
|
conflict: true,
|
|
20472
20964
|
existing_id: existing.id,
|
|
@@ -20752,7 +21244,7 @@ function updateSessionActivity(id, db2) {
|
|
|
20752
21244
|
const d = db2 || getDatabase3();
|
|
20753
21245
|
d.run("UPDATE sessions SET last_activity = ? WHERE id = ?", [now2(), id]);
|
|
20754
21246
|
}
|
|
20755
|
-
function
|
|
21247
|
+
function deleteSession2(id, db2) {
|
|
20756
21248
|
const d = db2 || getDatabase3();
|
|
20757
21249
|
const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
20758
21250
|
return result.changes > 0;
|
|
@@ -21571,13 +22063,13 @@ function searchTasks(options, projectId, taskListId, db2) {
|
|
|
21571
22063
|
return rows.map(rowToTask3);
|
|
21572
22064
|
}
|
|
21573
22065
|
function getTaskListDir(taskListId) {
|
|
21574
|
-
return
|
|
22066
|
+
return join44(HOME, ".claude", "tasks", taskListId);
|
|
21575
22067
|
}
|
|
21576
22068
|
function readClaudeTask(dir, filename) {
|
|
21577
|
-
return readJsonFile(
|
|
22069
|
+
return readJsonFile(join44(dir, filename));
|
|
21578
22070
|
}
|
|
21579
22071
|
function writeClaudeTask(dir, task) {
|
|
21580
|
-
writeJsonFile(
|
|
22072
|
+
writeJsonFile(join44(dir, `${task.id}.json`), task);
|
|
21581
22073
|
}
|
|
21582
22074
|
function toClaudeStatus(status) {
|
|
21583
22075
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -21589,14 +22081,14 @@ function toSqliteStatus(status) {
|
|
|
21589
22081
|
return status;
|
|
21590
22082
|
}
|
|
21591
22083
|
function readPrefixCounter(dir) {
|
|
21592
|
-
const path =
|
|
21593
|
-
if (!
|
|
22084
|
+
const path = join44(dir, ".prefix-counter");
|
|
22085
|
+
if (!existsSync43(path))
|
|
21594
22086
|
return 0;
|
|
21595
22087
|
const val = parseInt(readFileSync24(path, "utf-8").trim(), 10);
|
|
21596
22088
|
return isNaN(val) ? 0 : val;
|
|
21597
22089
|
}
|
|
21598
22090
|
function writePrefixCounter(dir, value) {
|
|
21599
|
-
writeFileSync23(
|
|
22091
|
+
writeFileSync23(join44(dir, ".prefix-counter"), String(value));
|
|
21600
22092
|
}
|
|
21601
22093
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
21602
22094
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -21623,7 +22115,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
21623
22115
|
}
|
|
21624
22116
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
21625
22117
|
const dir = getTaskListDir(taskListId);
|
|
21626
|
-
if (!
|
|
22118
|
+
if (!existsSync43(dir))
|
|
21627
22119
|
ensureDir22(dir);
|
|
21628
22120
|
const filter = {};
|
|
21629
22121
|
if (projectId)
|
|
@@ -21632,7 +22124,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
21632
22124
|
const existingByTodosId = new Map;
|
|
21633
22125
|
const files = listJsonFiles(dir);
|
|
21634
22126
|
for (const f of files) {
|
|
21635
|
-
const path =
|
|
22127
|
+
const path = join44(dir, f);
|
|
21636
22128
|
const ct = readClaudeTask(dir, f);
|
|
21637
22129
|
if (ct?.metadata?.["todos_id"]) {
|
|
21638
22130
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -21719,7 +22211,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
21719
22211
|
}
|
|
21720
22212
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
21721
22213
|
const dir = getTaskListDir(taskListId);
|
|
21722
|
-
if (!
|
|
22214
|
+
if (!existsSync43(dir)) {
|
|
21723
22215
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
21724
22216
|
}
|
|
21725
22217
|
const files = readdirSync22(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -21739,7 +22231,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
21739
22231
|
}
|
|
21740
22232
|
for (const f of files) {
|
|
21741
22233
|
try {
|
|
21742
|
-
const filePath =
|
|
22234
|
+
const filePath = join44(dir, f);
|
|
21743
22235
|
const ct = readClaudeTask(dir, f);
|
|
21744
22236
|
if (!ct)
|
|
21745
22237
|
continue;
|
|
@@ -22150,7 +22642,7 @@ function extractTodos(options, db2) {
|
|
|
22150
22642
|
for (const file of files) {
|
|
22151
22643
|
const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
|
|
22152
22644
|
try {
|
|
22153
|
-
const source =
|
|
22645
|
+
const source = readFileSync33(fullPath, "utf-8");
|
|
22154
22646
|
const relPath = statSync22(basePath).isFile() ? relative(resolve22(basePath, ".."), fullPath) : file;
|
|
22155
22647
|
const comments = extractFromSource(source, relPath, tags);
|
|
22156
22648
|
allComments.push(...comments);
|
|
@@ -23376,7 +23868,7 @@ import { homedir as homedir11 } from "os";
|
|
|
23376
23868
|
import { fileURLToPath } from "url";
|
|
23377
23869
|
import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
|
|
23378
23870
|
import { join as join25 } from "path";
|
|
23379
|
-
import { existsSync as existsSync34, readFileSync as
|
|
23871
|
+
import { existsSync as existsSync34, readFileSync as readFileSync34, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25 } from "fs";
|
|
23380
23872
|
import { join as join35, dirname as dirname23 } from "path";
|
|
23381
23873
|
import { homedir as homedir24 } from "os";
|
|
23382
23874
|
function getSkillsByCategory(category) {
|
|
@@ -24030,7 +24522,7 @@ function readConfigFile(path) {
|
|
|
24030
24522
|
if (!existsSync34(path))
|
|
24031
24523
|
return {};
|
|
24032
24524
|
try {
|
|
24033
|
-
const raw =
|
|
24525
|
+
const raw = readFileSync34(path, "utf-8");
|
|
24034
24526
|
const parsed = JSON.parse(raw);
|
|
24035
24527
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
24036
24528
|
return {};
|
|
@@ -24063,7 +24555,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
24063
24555
|
let existing = {};
|
|
24064
24556
|
if (existsSync34(filePath)) {
|
|
24065
24557
|
try {
|
|
24066
|
-
existing = JSON.parse(
|
|
24558
|
+
existing = JSON.parse(readFileSync34(filePath, "utf-8"));
|
|
24067
24559
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
24068
24560
|
existing = {};
|
|
24069
24561
|
}
|
|
@@ -26005,7 +26497,7 @@ ${snap.tree.slice(0, 2000)}`;
|
|
|
26005
26497
|
const response = await client.messages.create({
|
|
26006
26498
|
model,
|
|
26007
26499
|
max_tokens: 512,
|
|
26008
|
-
system:
|
|
26500
|
+
system: SYSTEM_PROMPT2,
|
|
26009
26501
|
messages: [{
|
|
26010
26502
|
role: "user",
|
|
26011
26503
|
content: `Task: ${task}
|
|
@@ -26070,7 +26562,7 @@ What actions should I take next? Return JSON array.`
|
|
|
26070
26562
|
}
|
|
26071
26563
|
return { success: false, result: null, steps_taken: steps.length, steps, cost_estimate: totalTokens / 1000 * 0.00025, error: `Reached max steps (${maxSteps}) without completing task` };
|
|
26072
26564
|
}
|
|
26073
|
-
var
|
|
26565
|
+
var SYSTEM_PROMPT2 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
|
|
26074
26566
|
|
|
26075
26567
|
Return a JSON array of at most 3 actions to execute next:
|
|
26076
26568
|
[{"tool": "navigate|click|type|scroll|evaluate|done", "args": {...}, "reason": "..."}]
|
|
@@ -30064,154 +30556,11 @@ import { join as join13 } from "path";
|
|
|
30064
30556
|
|
|
30065
30557
|
// src/lib/screenshot.ts
|
|
30066
30558
|
init_types();
|
|
30559
|
+
init_gallery();
|
|
30067
30560
|
var import_sharp = __toESM(require_lib(), 1);
|
|
30068
30561
|
import { join as join3 } from "path";
|
|
30069
30562
|
import { mkdirSync as mkdirSync3 } from "fs";
|
|
30070
30563
|
import { homedir as homedir3 } from "os";
|
|
30071
|
-
|
|
30072
|
-
// src/db/gallery.ts
|
|
30073
|
-
init_schema();
|
|
30074
|
-
import { randomUUID as randomUUID4 } from "crypto";
|
|
30075
|
-
function deserialize(row) {
|
|
30076
|
-
return {
|
|
30077
|
-
id: row.id,
|
|
30078
|
-
session_id: row.session_id ?? undefined,
|
|
30079
|
-
project_id: row.project_id ?? undefined,
|
|
30080
|
-
url: row.url ?? undefined,
|
|
30081
|
-
title: row.title ?? undefined,
|
|
30082
|
-
path: row.path,
|
|
30083
|
-
thumbnail_path: row.thumbnail_path ?? undefined,
|
|
30084
|
-
format: row.format ?? undefined,
|
|
30085
|
-
width: row.width ?? undefined,
|
|
30086
|
-
height: row.height ?? undefined,
|
|
30087
|
-
original_size_bytes: row.original_size_bytes ?? undefined,
|
|
30088
|
-
compressed_size_bytes: row.compressed_size_bytes ?? undefined,
|
|
30089
|
-
compression_ratio: row.compression_ratio ?? undefined,
|
|
30090
|
-
tags: JSON.parse(row.tags),
|
|
30091
|
-
notes: row.notes ?? undefined,
|
|
30092
|
-
is_favorite: row.is_favorite === 1,
|
|
30093
|
-
created_at: row.created_at
|
|
30094
|
-
};
|
|
30095
|
-
}
|
|
30096
|
-
function createEntry(data) {
|
|
30097
|
-
const db = getDatabase();
|
|
30098
|
-
const id = randomUUID4();
|
|
30099
|
-
db.prepare(`
|
|
30100
|
-
INSERT INTO gallery_entries
|
|
30101
|
-
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
30102
|
-
width, height, original_size_bytes, compressed_size_bytes, compression_ratio,
|
|
30103
|
-
tags, notes, is_favorite)
|
|
30104
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
30105
|
-
`).run(id, data.session_id ?? null, data.project_id ?? null, data.url ?? null, data.title ?? null, data.path, data.thumbnail_path ?? null, data.format ?? null, data.width ?? null, data.height ?? null, data.original_size_bytes ?? null, data.compressed_size_bytes ?? null, data.compression_ratio ?? null, JSON.stringify(data.tags ?? []), data.notes ?? null, data.is_favorite ? 1 : 0);
|
|
30106
|
-
return getEntry(id);
|
|
30107
|
-
}
|
|
30108
|
-
function getEntry(id) {
|
|
30109
|
-
const db = getDatabase();
|
|
30110
|
-
const row = db.query("SELECT * FROM gallery_entries WHERE id = ?").get(id);
|
|
30111
|
-
return row ? deserialize(row) : null;
|
|
30112
|
-
}
|
|
30113
|
-
function listEntries(filter) {
|
|
30114
|
-
const db = getDatabase();
|
|
30115
|
-
const conditions = [];
|
|
30116
|
-
const values = [];
|
|
30117
|
-
if (filter?.projectId) {
|
|
30118
|
-
conditions.push("project_id = ?");
|
|
30119
|
-
values.push(filter.projectId);
|
|
30120
|
-
}
|
|
30121
|
-
if (filter?.sessionId) {
|
|
30122
|
-
conditions.push("session_id = ?");
|
|
30123
|
-
values.push(filter.sessionId);
|
|
30124
|
-
}
|
|
30125
|
-
if (filter?.isFavorite !== undefined) {
|
|
30126
|
-
conditions.push("is_favorite = ?");
|
|
30127
|
-
values.push(filter.isFavorite ? 1 : 0);
|
|
30128
|
-
}
|
|
30129
|
-
if (filter?.dateFrom) {
|
|
30130
|
-
conditions.push("created_at >= ?");
|
|
30131
|
-
values.push(filter.dateFrom);
|
|
30132
|
-
}
|
|
30133
|
-
if (filter?.dateTo) {
|
|
30134
|
-
conditions.push("created_at <= ?");
|
|
30135
|
-
values.push(filter.dateTo);
|
|
30136
|
-
}
|
|
30137
|
-
if (filter?.tag) {
|
|
30138
|
-
conditions.push("tags LIKE ?");
|
|
30139
|
-
values.push(`%"${filter.tag}"%`);
|
|
30140
|
-
}
|
|
30141
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
30142
|
-
const limit = filter?.limit ?? 50;
|
|
30143
|
-
const offset = filter?.offset ?? 0;
|
|
30144
|
-
const rows = db.query(`SELECT * FROM gallery_entries ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...values, limit, offset);
|
|
30145
|
-
return rows.map(deserialize);
|
|
30146
|
-
}
|
|
30147
|
-
function updateEntry(id, data) {
|
|
30148
|
-
const db = getDatabase();
|
|
30149
|
-
const fields = [];
|
|
30150
|
-
const values = [];
|
|
30151
|
-
if (data.notes !== undefined) {
|
|
30152
|
-
fields.push("notes = ?");
|
|
30153
|
-
values.push(data.notes);
|
|
30154
|
-
}
|
|
30155
|
-
if (data.is_favorite !== undefined) {
|
|
30156
|
-
fields.push("is_favorite = ?");
|
|
30157
|
-
values.push(data.is_favorite ? 1 : 0);
|
|
30158
|
-
}
|
|
30159
|
-
if (data.tags !== undefined) {
|
|
30160
|
-
fields.push("tags = ?");
|
|
30161
|
-
values.push(JSON.stringify(data.tags));
|
|
30162
|
-
}
|
|
30163
|
-
if (fields.length === 0)
|
|
30164
|
-
return getEntry(id);
|
|
30165
|
-
values.push(id);
|
|
30166
|
-
db.prepare(`UPDATE gallery_entries SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
30167
|
-
return getEntry(id);
|
|
30168
|
-
}
|
|
30169
|
-
function deleteEntry(id) {
|
|
30170
|
-
const db = getDatabase();
|
|
30171
|
-
db.prepare("DELETE FROM gallery_entries WHERE id = ?").run(id);
|
|
30172
|
-
}
|
|
30173
|
-
function tagEntry(id, tag) {
|
|
30174
|
-
const entry = getEntry(id);
|
|
30175
|
-
if (!entry)
|
|
30176
|
-
return null;
|
|
30177
|
-
const tags = entry.tags.includes(tag) ? entry.tags : [...entry.tags, tag];
|
|
30178
|
-
return updateEntry(id, { tags });
|
|
30179
|
-
}
|
|
30180
|
-
function untagEntry(id, tag) {
|
|
30181
|
-
const entry = getEntry(id);
|
|
30182
|
-
if (!entry)
|
|
30183
|
-
return null;
|
|
30184
|
-
return updateEntry(id, { tags: entry.tags.filter((t) => t !== tag) });
|
|
30185
|
-
}
|
|
30186
|
-
function favoriteEntry(id, value) {
|
|
30187
|
-
return updateEntry(id, { is_favorite: value });
|
|
30188
|
-
}
|
|
30189
|
-
function searchEntries(q, limit = 20) {
|
|
30190
|
-
const db = getDatabase();
|
|
30191
|
-
const like = `%${q}%`;
|
|
30192
|
-
const rows = db.query(`
|
|
30193
|
-
SELECT * FROM gallery_entries
|
|
30194
|
-
WHERE url LIKE ? OR title LIKE ? OR notes LIKE ? OR tags LIKE ?
|
|
30195
|
-
ORDER BY created_at DESC LIMIT ${limit}
|
|
30196
|
-
`).all(like, like, like, like);
|
|
30197
|
-
return rows.map(deserialize);
|
|
30198
|
-
}
|
|
30199
|
-
function getGalleryStats(projectId) {
|
|
30200
|
-
const db = getDatabase();
|
|
30201
|
-
const where = projectId ? "WHERE project_id = ?" : "";
|
|
30202
|
-
const params = projectId ? [projectId] : [];
|
|
30203
|
-
const total = db.query(`SELECT COUNT(*) as count FROM gallery_entries ${where}`).get(...params)?.count ?? 0;
|
|
30204
|
-
const totalSize = db.query(`SELECT COALESCE(SUM(compressed_size_bytes), 0) as total FROM gallery_entries ${where}`).get(...params)?.total ?? 0;
|
|
30205
|
-
const favorites = db.query(`SELECT COUNT(*) as count FROM gallery_entries ${where ? where + " AND" : "WHERE"} is_favorite = 1`).get(...projectId ? [projectId] : [])?.count ?? 0;
|
|
30206
|
-
const formatRows = db.query(`SELECT format, COUNT(*) as count FROM gallery_entries ${where} GROUP BY format`).all(...params);
|
|
30207
|
-
const by_format = {};
|
|
30208
|
-
for (const row of formatRows) {
|
|
30209
|
-
by_format[row.format ?? "unknown"] = row.count;
|
|
30210
|
-
}
|
|
30211
|
-
return { total, total_size_bytes: totalSize, favorites, by_format };
|
|
30212
|
-
}
|
|
30213
|
-
|
|
30214
|
-
// src/lib/screenshot.ts
|
|
30215
30564
|
function getDataDir2() {
|
|
30216
30565
|
return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
|
|
30217
30566
|
}
|
|
@@ -30896,6 +31245,7 @@ function listProjects() {
|
|
|
30896
31245
|
// src/mcp/index.ts
|
|
30897
31246
|
init_network_log();
|
|
30898
31247
|
init_console_log();
|
|
31248
|
+
init_gallery();
|
|
30899
31249
|
|
|
30900
31250
|
// src/lib/downloads.ts
|
|
30901
31251
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
@@ -31118,6 +31468,18 @@ async function persistFile(localPath, opts) {
|
|
|
31118
31468
|
};
|
|
31119
31469
|
}
|
|
31120
31470
|
|
|
31471
|
+
// src/db/timeline.ts
|
|
31472
|
+
init_schema();
|
|
31473
|
+
function logEvent(sessionId, eventType, details = {}) {
|
|
31474
|
+
const db = getDatabase();
|
|
31475
|
+
const id = crypto.randomUUID();
|
|
31476
|
+
db.prepare("INSERT INTO session_events (id, session_id, event_type, details) VALUES (?, ?, ?, ?)").run(id, sessionId, eventType, JSON.stringify(details));
|
|
31477
|
+
}
|
|
31478
|
+
function getTimeline(sessionId, limit = 100) {
|
|
31479
|
+
const db = getDatabase();
|
|
31480
|
+
return db.query("SELECT * FROM session_events WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
31481
|
+
}
|
|
31482
|
+
|
|
31121
31483
|
// src/lib/tabs.ts
|
|
31122
31484
|
async function newTab(page, url) {
|
|
31123
31485
|
const context = page.context();
|
|
@@ -31218,11 +31580,39 @@ function err(e) {
|
|
|
31218
31580
|
isError: true
|
|
31219
31581
|
};
|
|
31220
31582
|
}
|
|
31583
|
+
async function errWithScreenshot(e, sessionId) {
|
|
31584
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
31585
|
+
const code = e instanceof BrowserError ? e.code : "ERROR";
|
|
31586
|
+
let screenshot_path;
|
|
31587
|
+
if (sessionId) {
|
|
31588
|
+
try {
|
|
31589
|
+
const sid = resolveSessionId(sessionId);
|
|
31590
|
+
const page = getSessionPage(sid);
|
|
31591
|
+
const result = await takeScreenshot(page, { maxWidth: 800, quality: 50, track: false, thumbnail: false });
|
|
31592
|
+
screenshot_path = result.path;
|
|
31593
|
+
} catch {}
|
|
31594
|
+
}
|
|
31595
|
+
return {
|
|
31596
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg, code, error_screenshot: screenshot_path }) }],
|
|
31597
|
+
isError: true
|
|
31598
|
+
};
|
|
31599
|
+
}
|
|
31600
|
+
function resolveSessionId(sessionId) {
|
|
31601
|
+
if (sessionId)
|
|
31602
|
+
return sessionId;
|
|
31603
|
+
const def = getDefaultSession();
|
|
31604
|
+
if (def)
|
|
31605
|
+
return def.session.id;
|
|
31606
|
+
const count = countActiveSessions2();
|
|
31607
|
+
if (count === 0)
|
|
31608
|
+
throw new BrowserError("No active sessions. Create one with browser_session_create first.", "NO_SESSION");
|
|
31609
|
+
throw new BrowserError(`${count} active sessions \u2014 specify session_id to choose one.`, "AMBIGUOUS_SESSION");
|
|
31610
|
+
}
|
|
31221
31611
|
var server = new McpServer({
|
|
31222
31612
|
name: "@hasna/browser",
|
|
31223
31613
|
version: "0.0.1"
|
|
31224
31614
|
});
|
|
31225
|
-
server.tool("browser_session_create", "Create a new browser session
|
|
31615
|
+
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.", {
|
|
31226
31616
|
engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
|
|
31227
31617
|
use_case: exports_external.string().optional(),
|
|
31228
31618
|
project_id: exports_external.string().optional(),
|
|
@@ -31231,9 +31621,17 @@ server.tool("browser_session_create", "Create a new browser session with the spe
|
|
|
31231
31621
|
headless: exports_external.boolean().optional().default(true),
|
|
31232
31622
|
viewport_width: exports_external.number().optional().default(1280),
|
|
31233
31623
|
viewport_height: exports_external.number().optional().default(720),
|
|
31234
|
-
stealth: exports_external.boolean().optional().default(false)
|
|
31235
|
-
|
|
31624
|
+
stealth: exports_external.boolean().optional().default(false),
|
|
31625
|
+
auto_gallery: exports_external.boolean().optional().default(false),
|
|
31626
|
+
force_new: exports_external.boolean().optional().default(false).describe("Force create a new session even if agent already has one"),
|
|
31627
|
+
tags: exports_external.array(exports_external.string()).optional()
|
|
31628
|
+
}, async ({ engine, use_case, project_id, agent_id, start_url, headless, viewport_width, viewport_height, stealth, auto_gallery, force_new, tags }) => {
|
|
31236
31629
|
try {
|
|
31630
|
+
if (agent_id && !force_new) {
|
|
31631
|
+
const existing = getActiveSessionForAgent2(agent_id);
|
|
31632
|
+
if (existing)
|
|
31633
|
+
return json({ session: existing.session, reused: true });
|
|
31634
|
+
}
|
|
31237
31635
|
const { session } = await createSession2({
|
|
31238
31636
|
engine,
|
|
31239
31637
|
useCase: use_case,
|
|
@@ -31242,44 +31640,66 @@ server.tool("browser_session_create", "Create a new browser session with the spe
|
|
|
31242
31640
|
startUrl: start_url,
|
|
31243
31641
|
headless,
|
|
31244
31642
|
viewport: { width: viewport_width, height: viewport_height },
|
|
31245
|
-
stealth
|
|
31643
|
+
stealth,
|
|
31644
|
+
autoGallery: auto_gallery
|
|
31246
31645
|
});
|
|
31247
|
-
|
|
31646
|
+
if (tags?.length) {
|
|
31647
|
+
const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
31648
|
+
for (const tag of tags)
|
|
31649
|
+
addSessionTag2(session.id, tag);
|
|
31650
|
+
}
|
|
31651
|
+
logEvent(session.id, "session_created", { engine: session.engine });
|
|
31652
|
+
return json({ session, reused: false });
|
|
31248
31653
|
} catch (e) {
|
|
31249
31654
|
return err(e);
|
|
31250
31655
|
}
|
|
31251
31656
|
});
|
|
31252
|
-
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 }) => {
|
|
31657
|
+
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 }) => {
|
|
31253
31658
|
try {
|
|
31659
|
+
if (tag) {
|
|
31660
|
+
const { listSessionsByTag: listSessionsByTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
31661
|
+
return json({ sessions: listSessionsByTag2(tag) });
|
|
31662
|
+
}
|
|
31254
31663
|
return json({ sessions: listSessions2({ status, projectId: project_id }) });
|
|
31255
31664
|
} catch (e) {
|
|
31256
31665
|
return err(e);
|
|
31257
31666
|
}
|
|
31258
31667
|
});
|
|
31259
|
-
server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
31668
|
+
server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31260
31669
|
try {
|
|
31261
|
-
const
|
|
31262
|
-
|
|
31263
|
-
|
|
31264
|
-
|
|
31265
|
-
|
|
31266
|
-
|
|
31670
|
+
const sid = resolveSessionId(session_id);
|
|
31671
|
+
const session = await closeSession2(sid);
|
|
31672
|
+
networkLogCleanup.get(sid)?.();
|
|
31673
|
+
consoleCaptureCleanup.get(sid)?.();
|
|
31674
|
+
networkLogCleanup.delete(sid);
|
|
31675
|
+
consoleCaptureCleanup.delete(sid);
|
|
31676
|
+
harCaptures.delete(sid);
|
|
31267
31677
|
return json({ session });
|
|
31268
31678
|
} catch (e) {
|
|
31269
31679
|
return err(e);
|
|
31270
31680
|
}
|
|
31271
31681
|
});
|
|
31682
|
+
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 }) => {
|
|
31683
|
+
try {
|
|
31684
|
+
const sid = resolveSessionId(session_id);
|
|
31685
|
+
const events = getTimeline(sid, limit);
|
|
31686
|
+
return json({ events, count: events.length });
|
|
31687
|
+
} catch (e) {
|
|
31688
|
+
return err(e);
|
|
31689
|
+
}
|
|
31690
|
+
});
|
|
31272
31691
|
server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
|
|
31273
|
-
session_id: exports_external.string(),
|
|
31692
|
+
session_id: exports_external.string().optional(),
|
|
31274
31693
|
url: exports_external.string(),
|
|
31275
31694
|
timeout: exports_external.number().optional().default(30000),
|
|
31276
31695
|
auto_snapshot: exports_external.boolean().optional().default(true),
|
|
31277
31696
|
auto_thumbnail: exports_external.boolean().optional().default(true)
|
|
31278
31697
|
}, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
|
|
31279
31698
|
try {
|
|
31280
|
-
const
|
|
31281
|
-
|
|
31282
|
-
|
|
31699
|
+
const sid = resolveSessionId(session_id);
|
|
31700
|
+
const page = getSessionPage(sid);
|
|
31701
|
+
if (isBunSession(sid)) {
|
|
31702
|
+
const bunView = getSessionBunView(sid);
|
|
31283
31703
|
await bunView.goto(url, { timeout });
|
|
31284
31704
|
await new Promise((r) => setTimeout(r, 500));
|
|
31285
31705
|
} else {
|
|
@@ -31306,10 +31726,10 @@ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto
|
|
|
31306
31726
|
} catch {}
|
|
31307
31727
|
}
|
|
31308
31728
|
try {
|
|
31309
|
-
const session = getSession2(
|
|
31729
|
+
const session = getSession2(sid);
|
|
31310
31730
|
if (!session.name) {
|
|
31311
31731
|
const hostname = new URL(current_url).hostname;
|
|
31312
|
-
renameSession2(
|
|
31732
|
+
renameSession2(sid, hostname);
|
|
31313
31733
|
}
|
|
31314
31734
|
} catch {}
|
|
31315
31735
|
const result = {
|
|
@@ -31325,86 +31745,104 @@ server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto
|
|
|
31325
31745
|
result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
|
|
31326
31746
|
} catch {}
|
|
31327
31747
|
}
|
|
31328
|
-
if (
|
|
31748
|
+
if (isAutoGallery(sid)) {
|
|
31749
|
+
try {
|
|
31750
|
+
const ss = await takeScreenshot(page, { maxWidth: 1280, quality: 70, thumbnail: true });
|
|
31751
|
+
const { createEntry: createEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
31752
|
+
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 });
|
|
31753
|
+
} catch {}
|
|
31754
|
+
}
|
|
31755
|
+
if (isBunSession(sid) && auto_snapshot) {
|
|
31329
31756
|
await new Promise((r) => setTimeout(r, 200));
|
|
31330
31757
|
}
|
|
31331
31758
|
if (auto_snapshot) {
|
|
31332
31759
|
try {
|
|
31333
|
-
const snap = await takeSnapshot(page,
|
|
31334
|
-
setLastSnapshot(
|
|
31760
|
+
const snap = await takeSnapshot(page, sid);
|
|
31761
|
+
setLastSnapshot(sid, snap);
|
|
31335
31762
|
const refEntries = Object.entries(snap.refs).slice(0, 30);
|
|
31336
31763
|
result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
31337
31764
|
result.interactive_count = snap.interactive_count;
|
|
31338
|
-
result.has_errors = getConsoleLog(
|
|
31765
|
+
result.has_errors = getConsoleLog(sid, "error").length > 0;
|
|
31339
31766
|
} catch {}
|
|
31340
31767
|
}
|
|
31768
|
+
logEvent(sid, "navigate", { url, title, current_url });
|
|
31341
31769
|
return json(result);
|
|
31342
31770
|
} catch (e) {
|
|
31343
|
-
return
|
|
31771
|
+
return errWithScreenshot(e, session_id);
|
|
31344
31772
|
}
|
|
31345
31773
|
});
|
|
31346
|
-
server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
31774
|
+
server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31347
31775
|
try {
|
|
31348
|
-
const
|
|
31776
|
+
const sid = resolveSessionId(session_id);
|
|
31777
|
+
const page = getSessionPage(sid);
|
|
31349
31778
|
await goBack(page);
|
|
31350
31779
|
return json({ url: page.url() });
|
|
31351
31780
|
} catch (e) {
|
|
31352
31781
|
return err(e);
|
|
31353
31782
|
}
|
|
31354
31783
|
});
|
|
31355
|
-
server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
31784
|
+
server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31356
31785
|
try {
|
|
31357
|
-
const
|
|
31786
|
+
const sid = resolveSessionId(session_id);
|
|
31787
|
+
const page = getSessionPage(sid);
|
|
31358
31788
|
await goForward(page);
|
|
31359
31789
|
return json({ url: page.url() });
|
|
31360
31790
|
} catch (e) {
|
|
31361
31791
|
return err(e);
|
|
31362
31792
|
}
|
|
31363
31793
|
});
|
|
31364
|
-
server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
31794
|
+
server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31365
31795
|
try {
|
|
31366
|
-
const
|
|
31796
|
+
const sid = resolveSessionId(session_id);
|
|
31797
|
+
const page = getSessionPage(sid);
|
|
31367
31798
|
await reload(page);
|
|
31368
31799
|
return json({ url: page.url() });
|
|
31369
31800
|
} catch (e) {
|
|
31370
31801
|
return err(e);
|
|
31371
31802
|
}
|
|
31372
31803
|
});
|
|
31373
|
-
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 }) => {
|
|
31804
|
+
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 }) => {
|
|
31374
31805
|
try {
|
|
31375
|
-
const
|
|
31806
|
+
const sid = resolveSessionId(session_id);
|
|
31807
|
+
const page = getSessionPage(sid);
|
|
31376
31808
|
if (ref) {
|
|
31377
|
-
await clickRef(page,
|
|
31809
|
+
await clickRef(page, sid, ref, { timeout });
|
|
31810
|
+
logEvent(sid, "click", { selector: ref, method: "ref" });
|
|
31378
31811
|
return json({ clicked: ref, method: "ref" });
|
|
31379
31812
|
}
|
|
31380
31813
|
if (!selector)
|
|
31381
31814
|
return err(new Error("Either ref or selector is required"));
|
|
31382
31815
|
await click(page, selector, { button, timeout });
|
|
31816
|
+
logEvent(sid, "click", { selector, method: "selector" });
|
|
31383
31817
|
return json({ clicked: selector, method: "selector" });
|
|
31384
31818
|
} catch (e) {
|
|
31385
|
-
return
|
|
31819
|
+
return errWithScreenshot(e, session_id);
|
|
31386
31820
|
}
|
|
31387
31821
|
});
|
|
31388
|
-
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 }) => {
|
|
31822
|
+
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 }) => {
|
|
31389
31823
|
try {
|
|
31390
|
-
const
|
|
31824
|
+
const sid = resolveSessionId(session_id);
|
|
31825
|
+
const page = getSessionPage(sid);
|
|
31391
31826
|
if (ref) {
|
|
31392
|
-
await typeRef(page,
|
|
31827
|
+
await typeRef(page, sid, ref, text, { clear, delay });
|
|
31828
|
+
logEvent(sid, "type", { selector: ref, text: text.slice(0, 100) });
|
|
31393
31829
|
return json({ typed: text, ref, method: "ref" });
|
|
31394
31830
|
}
|
|
31395
31831
|
if (!selector)
|
|
31396
31832
|
return err(new Error("Either ref or selector is required"));
|
|
31397
31833
|
await type(page, selector, text, { clear, delay });
|
|
31834
|
+
logEvent(sid, "type", { selector, text: text.slice(0, 100) });
|
|
31398
31835
|
return json({ typed: text, selector, method: "selector" });
|
|
31399
31836
|
} catch (e) {
|
|
31400
|
-
return
|
|
31837
|
+
return errWithScreenshot(e, session_id);
|
|
31401
31838
|
}
|
|
31402
31839
|
});
|
|
31403
|
-
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 }) => {
|
|
31840
|
+
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 }) => {
|
|
31404
31841
|
try {
|
|
31405
|
-
const
|
|
31842
|
+
const sid = resolveSessionId(session_id);
|
|
31843
|
+
const page = getSessionPage(sid);
|
|
31406
31844
|
if (ref) {
|
|
31407
|
-
await hoverRef(page,
|
|
31845
|
+
await hoverRef(page, sid, ref);
|
|
31408
31846
|
return json({ hovered: ref, method: "ref" });
|
|
31409
31847
|
}
|
|
31410
31848
|
if (!selector)
|
|
@@ -31415,20 +31853,22 @@ server.tool("browser_hover", "Hover over an element by ref or selector", { sessi
|
|
|
31415
31853
|
return err(e);
|
|
31416
31854
|
}
|
|
31417
31855
|
});
|
|
31418
|
-
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 }) => {
|
|
31856
|
+
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 }) => {
|
|
31419
31857
|
try {
|
|
31420
|
-
const
|
|
31858
|
+
const sid = resolveSessionId(session_id);
|
|
31859
|
+
const page = getSessionPage(sid);
|
|
31421
31860
|
await scroll(page, direction, amount);
|
|
31422
31861
|
return json({ scrolled: direction, amount });
|
|
31423
31862
|
} catch (e) {
|
|
31424
31863
|
return err(e);
|
|
31425
31864
|
}
|
|
31426
31865
|
});
|
|
31427
|
-
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 }) => {
|
|
31866
|
+
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 }) => {
|
|
31428
31867
|
try {
|
|
31429
|
-
const
|
|
31868
|
+
const sid = resolveSessionId(session_id);
|
|
31869
|
+
const page = getSessionPage(sid);
|
|
31430
31870
|
if (ref) {
|
|
31431
|
-
const selected2 = await selectRef(page,
|
|
31871
|
+
const selected2 = await selectRef(page, sid, ref, value);
|
|
31432
31872
|
return json({ selected: selected2, method: "ref" });
|
|
31433
31873
|
}
|
|
31434
31874
|
if (!selector)
|
|
@@ -31439,11 +31879,12 @@ server.tool("browser_select", "Select a dropdown option by ref or selector", { s
|
|
|
31439
31879
|
return err(e);
|
|
31440
31880
|
}
|
|
31441
31881
|
});
|
|
31442
|
-
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 }) => {
|
|
31882
|
+
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 }) => {
|
|
31443
31883
|
try {
|
|
31444
|
-
const
|
|
31884
|
+
const sid = resolveSessionId(session_id);
|
|
31885
|
+
const page = getSessionPage(sid);
|
|
31445
31886
|
if (ref) {
|
|
31446
|
-
await checkRef(page,
|
|
31887
|
+
await checkRef(page, sid, ref, checked);
|
|
31447
31888
|
return json({ checked, ref, method: "ref" });
|
|
31448
31889
|
}
|
|
31449
31890
|
if (!selector)
|
|
@@ -31454,52 +31895,58 @@ server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector",
|
|
|
31454
31895
|
return err(e);
|
|
31455
31896
|
}
|
|
31456
31897
|
});
|
|
31457
|
-
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 }) => {
|
|
31898
|
+
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 }) => {
|
|
31458
31899
|
try {
|
|
31459
|
-
const
|
|
31900
|
+
const sid = resolveSessionId(session_id);
|
|
31901
|
+
const page = getSessionPage(sid);
|
|
31460
31902
|
await uploadFile(page, selector, file_path);
|
|
31461
31903
|
return json({ uploaded: file_path, selector });
|
|
31462
31904
|
} catch (e) {
|
|
31463
31905
|
return err(e);
|
|
31464
31906
|
}
|
|
31465
31907
|
});
|
|
31466
|
-
server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string(), key: exports_external.string() }, async ({ session_id, key }) => {
|
|
31908
|
+
server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string().optional(), key: exports_external.string() }, async ({ session_id, key }) => {
|
|
31467
31909
|
try {
|
|
31468
|
-
const
|
|
31910
|
+
const sid = resolveSessionId(session_id);
|
|
31911
|
+
const page = getSessionPage(sid);
|
|
31469
31912
|
await pressKey(page, key);
|
|
31470
31913
|
return json({ pressed: key });
|
|
31471
31914
|
} catch (e) {
|
|
31472
31915
|
return err(e);
|
|
31473
31916
|
}
|
|
31474
31917
|
});
|
|
31475
|
-
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 }) => {
|
|
31918
|
+
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 }) => {
|
|
31476
31919
|
try {
|
|
31477
|
-
const
|
|
31920
|
+
const sid = resolveSessionId(session_id);
|
|
31921
|
+
const page = getSessionPage(sid);
|
|
31478
31922
|
await waitForSelector(page, selector, { state, timeout });
|
|
31479
31923
|
return json({ ready: selector });
|
|
31480
31924
|
} catch (e) {
|
|
31481
31925
|
return err(e);
|
|
31482
31926
|
}
|
|
31483
31927
|
});
|
|
31484
|
-
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 }) => {
|
|
31928
|
+
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 }) => {
|
|
31485
31929
|
try {
|
|
31486
|
-
const
|
|
31930
|
+
const sid = resolveSessionId(session_id);
|
|
31931
|
+
const page = getSessionPage(sid);
|
|
31487
31932
|
return json({ text: await getText(page, selector) });
|
|
31488
31933
|
} catch (e) {
|
|
31489
31934
|
return err(e);
|
|
31490
31935
|
}
|
|
31491
31936
|
});
|
|
31492
|
-
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 }) => {
|
|
31937
|
+
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 }) => {
|
|
31493
31938
|
try {
|
|
31494
|
-
const
|
|
31939
|
+
const sid = resolveSessionId(session_id);
|
|
31940
|
+
const page = getSessionPage(sid);
|
|
31495
31941
|
return json({ html: await getHTML(page, selector) });
|
|
31496
31942
|
} catch (e) {
|
|
31497
31943
|
return err(e);
|
|
31498
31944
|
}
|
|
31499
31945
|
});
|
|
31500
|
-
server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
31946
|
+
server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31501
31947
|
try {
|
|
31502
|
-
const
|
|
31948
|
+
const sid = resolveSessionId(session_id);
|
|
31949
|
+
const page = getSessionPage(sid);
|
|
31503
31950
|
const links = await getLinks(page);
|
|
31504
31951
|
return json({ links, count: links.length });
|
|
31505
31952
|
} catch (e) {
|
|
@@ -31507,22 +31954,24 @@ server.tool("browser_get_links", "Get all links from the current page", { sessio
|
|
|
31507
31954
|
}
|
|
31508
31955
|
});
|
|
31509
31956
|
server.tool("browser_extract", "Extract content from the page in a specified format", {
|
|
31510
|
-
session_id: exports_external.string(),
|
|
31957
|
+
session_id: exports_external.string().optional(),
|
|
31511
31958
|
format: exports_external.enum(["text", "html", "links", "table", "structured"]).optional().default("text"),
|
|
31512
31959
|
selector: exports_external.string().optional(),
|
|
31513
31960
|
schema: exports_external.record(exports_external.string()).optional()
|
|
31514
31961
|
}, async ({ session_id, format, selector, schema }) => {
|
|
31515
31962
|
try {
|
|
31516
|
-
const
|
|
31963
|
+
const sid = resolveSessionId(session_id);
|
|
31964
|
+
const page = getSessionPage(sid);
|
|
31517
31965
|
const result = await extract(page, { format, selector, schema });
|
|
31518
31966
|
return json(result);
|
|
31519
31967
|
} catch (e) {
|
|
31520
31968
|
return err(e);
|
|
31521
31969
|
}
|
|
31522
31970
|
});
|
|
31523
|
-
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 }) => {
|
|
31971
|
+
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 }) => {
|
|
31524
31972
|
try {
|
|
31525
|
-
const
|
|
31973
|
+
const sid = resolveSessionId(session_id);
|
|
31974
|
+
const page = getSessionPage(sid);
|
|
31526
31975
|
const elements = await findElements(page, selector);
|
|
31527
31976
|
const texts = await Promise.all(elements.map((el) => el.textContent()));
|
|
31528
31977
|
return json({ count: elements.length, texts });
|
|
@@ -31531,15 +31980,16 @@ server.tool("browser_find", "Find elements matching a selector and return their
|
|
|
31531
31980
|
}
|
|
31532
31981
|
});
|
|
31533
31982
|
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.", {
|
|
31534
|
-
session_id: exports_external.string(),
|
|
31983
|
+
session_id: exports_external.string().optional(),
|
|
31535
31984
|
compact: exports_external.boolean().optional().default(true),
|
|
31536
31985
|
max_refs: exports_external.number().optional().default(50),
|
|
31537
31986
|
full_tree: exports_external.boolean().optional().default(false)
|
|
31538
31987
|
}, async ({ session_id, compact, max_refs, full_tree }) => {
|
|
31539
31988
|
try {
|
|
31540
|
-
const
|
|
31541
|
-
const
|
|
31542
|
-
|
|
31989
|
+
const sid = resolveSessionId(session_id);
|
|
31990
|
+
const page = getSessionPage(sid);
|
|
31991
|
+
const result = await takeSnapshot(page, sid);
|
|
31992
|
+
setLastSnapshot(sid, result);
|
|
31543
31993
|
const refEntries = Object.entries(result.refs).slice(0, max_refs);
|
|
31544
31994
|
const limitedRefs = Object.fromEntries(refEntries);
|
|
31545
31995
|
const truncated = Object.keys(result.refs).length > max_refs;
|
|
@@ -31562,21 +32012,22 @@ server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@
|
|
|
31562
32012
|
}
|
|
31563
32013
|
});
|
|
31564
32014
|
server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overlay numbered labels on interactive elements for visual+ref workflows.", {
|
|
31565
|
-
session_id: exports_external.string(),
|
|
32015
|
+
session_id: exports_external.string().optional(),
|
|
31566
32016
|
selector: exports_external.string().optional(),
|
|
31567
32017
|
full_page: exports_external.boolean().optional().default(false),
|
|
31568
32018
|
format: exports_external.enum(["png", "jpeg", "webp"]).optional().default("webp"),
|
|
31569
|
-
quality: exports_external.number().optional(),
|
|
31570
|
-
max_width: exports_external.number().optional().default(
|
|
32019
|
+
quality: exports_external.number().optional().default(60),
|
|
32020
|
+
max_width: exports_external.number().optional().default(800),
|
|
31571
32021
|
compress: exports_external.boolean().optional().default(true),
|
|
31572
32022
|
thumbnail: exports_external.boolean().optional().default(true),
|
|
31573
32023
|
annotate: exports_external.boolean().optional().default(false)
|
|
31574
32024
|
}, async ({ session_id, selector, full_page, format, quality, max_width, compress, thumbnail, annotate }) => {
|
|
31575
32025
|
try {
|
|
31576
|
-
const
|
|
32026
|
+
const sid = resolveSessionId(session_id);
|
|
32027
|
+
const page = getSessionPage(sid);
|
|
31577
32028
|
if (annotate && !selector && !full_page) {
|
|
31578
32029
|
const { annotateScreenshot: annotateScreenshot2 } = await Promise.resolve().then(() => (init_annotate(), exports_annotate));
|
|
31579
|
-
const annotated = await annotateScreenshot2(page,
|
|
32030
|
+
const annotated = await annotateScreenshot2(page, sid);
|
|
31580
32031
|
const base64 = annotated.buffer.toString("base64");
|
|
31581
32032
|
return json({
|
|
31582
32033
|
base64: base64.length > 50000 ? undefined : base64,
|
|
@@ -31592,32 +32043,35 @@ server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overl
|
|
|
31592
32043
|
try {
|
|
31593
32044
|
const buf = Buffer.from(result.base64, "base64");
|
|
31594
32045
|
const filename = result.path.split("/").pop() ?? `screenshot.${format ?? "webp"}`;
|
|
31595
|
-
const dl = saveToDownloads(buf, filename, { sessionId:
|
|
32046
|
+
const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "screenshot", sourceUrl: page.url() });
|
|
31596
32047
|
result.download_id = dl.id;
|
|
31597
32048
|
} catch {}
|
|
31598
|
-
|
|
32049
|
+
result.estimated_tokens = Math.ceil(result.base64.length / 4);
|
|
32050
|
+
if (result.base64.length > 20000) {
|
|
31599
32051
|
result.base64_truncated = true;
|
|
31600
32052
|
result.full_image_path = result.path;
|
|
31601
32053
|
result.base64 = result.thumbnail_base64 ?? "";
|
|
31602
32054
|
}
|
|
32055
|
+
logEvent(sid, "screenshot", { path: result.path });
|
|
31603
32056
|
return json(result);
|
|
31604
32057
|
} catch (e) {
|
|
31605
32058
|
return err(e);
|
|
31606
32059
|
}
|
|
31607
32060
|
});
|
|
31608
32061
|
server.tool("browser_pdf", "Generate a PDF of the current page", {
|
|
31609
|
-
session_id: exports_external.string(),
|
|
32062
|
+
session_id: exports_external.string().optional(),
|
|
31610
32063
|
format: exports_external.enum(["A4", "Letter", "A3", "A5"]).optional().default("A4"),
|
|
31611
32064
|
landscape: exports_external.boolean().optional().default(false),
|
|
31612
32065
|
print_background: exports_external.boolean().optional().default(true)
|
|
31613
32066
|
}, async ({ session_id, format, landscape, print_background }) => {
|
|
31614
32067
|
try {
|
|
31615
|
-
const
|
|
32068
|
+
const sid = resolveSessionId(session_id);
|
|
32069
|
+
const page = getSessionPage(sid);
|
|
31616
32070
|
const result = await generatePDF(page, { format, landscape, printBackground: print_background });
|
|
31617
32071
|
try {
|
|
31618
32072
|
const buf = Buffer.from(result.base64, "base64");
|
|
31619
32073
|
const filename = result.path.split("/").pop() ?? "document.pdf";
|
|
31620
|
-
const dl = saveToDownloads(buf, filename, { sessionId:
|
|
32074
|
+
const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "pdf", sourceUrl: page.url() });
|
|
31621
32075
|
result.download_id = dl.id;
|
|
31622
32076
|
} catch {}
|
|
31623
32077
|
return json(result);
|
|
@@ -31625,25 +32079,27 @@ server.tool("browser_pdf", "Generate a PDF of the current page", {
|
|
|
31625
32079
|
return err(e);
|
|
31626
32080
|
}
|
|
31627
32081
|
});
|
|
31628
|
-
server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string(), script: exports_external.string() }, async ({ session_id, script }) => {
|
|
32082
|
+
server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string().optional(), script: exports_external.string() }, async ({ session_id, script }) => {
|
|
31629
32083
|
try {
|
|
31630
|
-
const
|
|
32084
|
+
const sid = resolveSessionId(session_id);
|
|
32085
|
+
const page = getSessionPage(sid);
|
|
31631
32086
|
const result = await page.evaluate(script);
|
|
31632
32087
|
return json({ result });
|
|
31633
32088
|
} catch (e) {
|
|
31634
|
-
return
|
|
32089
|
+
return errWithScreenshot(e, session_id);
|
|
31635
32090
|
}
|
|
31636
32091
|
});
|
|
31637
|
-
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 }) => {
|
|
32092
|
+
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 }) => {
|
|
31638
32093
|
try {
|
|
31639
|
-
const
|
|
32094
|
+
const sid = resolveSessionId(session_id);
|
|
32095
|
+
const page = getSessionPage(sid);
|
|
31640
32096
|
return json({ cookies: await getCookies(page, { name, domain }) });
|
|
31641
32097
|
} catch (e) {
|
|
31642
32098
|
return err(e);
|
|
31643
32099
|
}
|
|
31644
32100
|
});
|
|
31645
32101
|
server.tool("browser_cookies_set", "Set a cookie in the current session", {
|
|
31646
|
-
session_id: exports_external.string(),
|
|
32102
|
+
session_id: exports_external.string().optional(),
|
|
31647
32103
|
name: exports_external.string(),
|
|
31648
32104
|
value: exports_external.string(),
|
|
31649
32105
|
domain: exports_external.string().optional(),
|
|
@@ -31653,7 +32109,8 @@ server.tool("browser_cookies_set", "Set a cookie in the current session", {
|
|
|
31653
32109
|
secure: exports_external.boolean().optional().default(false)
|
|
31654
32110
|
}, async ({ session_id, name, value, domain, path, expires, http_only, secure }) => {
|
|
31655
32111
|
try {
|
|
31656
|
-
const
|
|
32112
|
+
const sid = resolveSessionId(session_id);
|
|
32113
|
+
const page = getSessionPage(sid);
|
|
31657
32114
|
await setCookie(page, {
|
|
31658
32115
|
name,
|
|
31659
32116
|
value,
|
|
@@ -31669,27 +32126,30 @@ server.tool("browser_cookies_set", "Set a cookie in the current session", {
|
|
|
31669
32126
|
return err(e);
|
|
31670
32127
|
}
|
|
31671
32128
|
});
|
|
31672
|
-
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 }) => {
|
|
32129
|
+
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 }) => {
|
|
31673
32130
|
try {
|
|
31674
|
-
const
|
|
32131
|
+
const sid = resolveSessionId(session_id);
|
|
32132
|
+
const page = getSessionPage(sid);
|
|
31675
32133
|
await clearCookies(page, name || domain ? { name, domain } : undefined);
|
|
31676
32134
|
return json({ cleared: true });
|
|
31677
32135
|
} catch (e) {
|
|
31678
32136
|
return err(e);
|
|
31679
32137
|
}
|
|
31680
32138
|
});
|
|
31681
|
-
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 }) => {
|
|
32139
|
+
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 }) => {
|
|
31682
32140
|
try {
|
|
31683
|
-
const
|
|
32141
|
+
const sid = resolveSessionId(session_id);
|
|
32142
|
+
const page = getSessionPage(sid);
|
|
31684
32143
|
const value = storage_type === "session" ? await getSessionStorage(page, key) : await getLocalStorage(page, key);
|
|
31685
32144
|
return json({ value });
|
|
31686
32145
|
} catch (e) {
|
|
31687
32146
|
return err(e);
|
|
31688
32147
|
}
|
|
31689
32148
|
});
|
|
31690
|
-
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 }) => {
|
|
32149
|
+
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 }) => {
|
|
31691
32150
|
try {
|
|
31692
|
-
const
|
|
32151
|
+
const sid = resolveSessionId(session_id);
|
|
32152
|
+
const page = getSessionPage(sid);
|
|
31693
32153
|
if (storage_type === "session") {
|
|
31694
32154
|
await setSessionStorage(page, key, value);
|
|
31695
32155
|
} else {
|
|
@@ -31700,28 +32160,30 @@ server.tool("browser_storage_set", "Set a localStorage or sessionStorage value",
|
|
|
31700
32160
|
return err(e);
|
|
31701
32161
|
}
|
|
31702
32162
|
});
|
|
31703
|
-
server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32163
|
+
server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31704
32164
|
try {
|
|
31705
|
-
|
|
31706
|
-
|
|
31707
|
-
const
|
|
31708
|
-
|
|
32165
|
+
const sid = resolveSessionId(session_id);
|
|
32166
|
+
if (!networkLogCleanup.has(sid)) {
|
|
32167
|
+
const page = getSessionPage(sid);
|
|
32168
|
+
const cleanup = enableNetworkLogging(page, sid);
|
|
32169
|
+
networkLogCleanup.set(sid, cleanup);
|
|
31709
32170
|
}
|
|
31710
|
-
const log = getNetworkLog(
|
|
32171
|
+
const log = getNetworkLog(sid);
|
|
31711
32172
|
return json({ requests: log, count: log.length });
|
|
31712
32173
|
} catch (e) {
|
|
31713
32174
|
return err(e);
|
|
31714
32175
|
}
|
|
31715
32176
|
});
|
|
31716
32177
|
server.tool("browser_network_intercept", "Add a network interception rule", {
|
|
31717
|
-
session_id: exports_external.string(),
|
|
32178
|
+
session_id: exports_external.string().optional(),
|
|
31718
32179
|
pattern: exports_external.string(),
|
|
31719
32180
|
action: exports_external.enum(["block", "modify", "log"]),
|
|
31720
32181
|
response_status: exports_external.number().optional(),
|
|
31721
32182
|
response_body: exports_external.string().optional()
|
|
31722
32183
|
}, async ({ session_id, pattern, action, response_status, response_body }) => {
|
|
31723
32184
|
try {
|
|
31724
|
-
const
|
|
32185
|
+
const sid = resolveSessionId(session_id);
|
|
32186
|
+
const page = getSessionPage(sid);
|
|
31725
32187
|
await addInterceptRule(page, {
|
|
31726
32188
|
pattern,
|
|
31727
32189
|
action,
|
|
@@ -31732,27 +32194,29 @@ server.tool("browser_network_intercept", "Add a network interception rule", {
|
|
|
31732
32194
|
return err(e);
|
|
31733
32195
|
}
|
|
31734
32196
|
});
|
|
31735
|
-
server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32197
|
+
server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31736
32198
|
try {
|
|
31737
|
-
const
|
|
32199
|
+
const sid = resolveSessionId(session_id);
|
|
32200
|
+
const page = getSessionPage(sid);
|
|
31738
32201
|
const capture = startHAR(page);
|
|
31739
|
-
harCaptures.set(
|
|
32202
|
+
harCaptures.set(sid, capture);
|
|
31740
32203
|
return json({ started: true });
|
|
31741
32204
|
} catch (e) {
|
|
31742
32205
|
return err(e);
|
|
31743
32206
|
}
|
|
31744
32207
|
});
|
|
31745
|
-
server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32208
|
+
server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31746
32209
|
try {
|
|
31747
|
-
const
|
|
32210
|
+
const sid = resolveSessionId(session_id);
|
|
32211
|
+
const capture = harCaptures.get(sid);
|
|
31748
32212
|
if (!capture)
|
|
31749
32213
|
return err(new Error("No active HAR capture for this session"));
|
|
31750
32214
|
const har = capture.stop();
|
|
31751
|
-
harCaptures.delete(
|
|
32215
|
+
harCaptures.delete(sid);
|
|
31752
32216
|
let download_id;
|
|
31753
32217
|
try {
|
|
31754
32218
|
const harBuf = Buffer.from(JSON.stringify(har, null, 2));
|
|
31755
|
-
const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId:
|
|
32219
|
+
const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId: sid, type: "har" });
|
|
31756
32220
|
download_id = dl.id;
|
|
31757
32221
|
} catch {}
|
|
31758
32222
|
return json({ har, entry_count: har.log.entries.length, download_id });
|
|
@@ -31760,32 +32224,35 @@ server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { se
|
|
|
31760
32224
|
return err(e);
|
|
31761
32225
|
}
|
|
31762
32226
|
});
|
|
31763
|
-
server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32227
|
+
server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31764
32228
|
try {
|
|
31765
|
-
const
|
|
32229
|
+
const sid = resolveSessionId(session_id);
|
|
32230
|
+
const page = getSessionPage(sid);
|
|
31766
32231
|
const metrics = await getPerformanceMetrics(page);
|
|
31767
32232
|
return json({ metrics });
|
|
31768
32233
|
} catch (e) {
|
|
31769
32234
|
return err(e);
|
|
31770
32235
|
}
|
|
31771
32236
|
});
|
|
31772
|
-
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 }) => {
|
|
32237
|
+
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 }) => {
|
|
31773
32238
|
try {
|
|
31774
|
-
|
|
31775
|
-
|
|
31776
|
-
const
|
|
31777
|
-
|
|
32239
|
+
const sid = resolveSessionId(session_id);
|
|
32240
|
+
if (!consoleCaptureCleanup.has(sid)) {
|
|
32241
|
+
const page = getSessionPage(sid);
|
|
32242
|
+
const cleanup = enableConsoleCapture(page, sid);
|
|
32243
|
+
consoleCaptureCleanup.set(sid, cleanup);
|
|
31778
32244
|
}
|
|
31779
|
-
const messages = getConsoleLog(
|
|
32245
|
+
const messages = getConsoleLog(sid, level);
|
|
31780
32246
|
return json({ messages, count: messages.length });
|
|
31781
32247
|
} catch (e) {
|
|
31782
32248
|
return err(e);
|
|
31783
32249
|
}
|
|
31784
32250
|
});
|
|
31785
|
-
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 }) => {
|
|
32251
|
+
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 }) => {
|
|
31786
32252
|
try {
|
|
31787
|
-
const
|
|
31788
|
-
const
|
|
32253
|
+
const sid = resolveSessionId(session_id);
|
|
32254
|
+
const page = getSessionPage(sid);
|
|
32255
|
+
const recording = startRecording(sid, name, page.url());
|
|
31789
32256
|
return json({ recording_id: recording.id, name: recording.name });
|
|
31790
32257
|
} catch (e) {
|
|
31791
32258
|
return err(e);
|
|
@@ -31813,9 +32280,10 @@ server.tool("browser_record_stop", "Stop recording and save the recording", { re
|
|
|
31813
32280
|
return err(e);
|
|
31814
32281
|
}
|
|
31815
32282
|
});
|
|
31816
|
-
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 }) => {
|
|
32283
|
+
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 }) => {
|
|
31817
32284
|
try {
|
|
31818
|
-
const
|
|
32285
|
+
const sid = resolveSessionId(session_id);
|
|
32286
|
+
const page = getSessionPage(sid);
|
|
31819
32287
|
const result = await replayRecording(recording_id, page);
|
|
31820
32288
|
return json(result);
|
|
31821
32289
|
} catch (e) {
|
|
@@ -31853,7 +32321,7 @@ server.tool("browser_crawl", "Crawl a URL recursively and return discovered page
|
|
|
31853
32321
|
server.tool("browser_register_agent", "Register an agent with the browser service", {
|
|
31854
32322
|
name: exports_external.string(),
|
|
31855
32323
|
description: exports_external.string().optional(),
|
|
31856
|
-
session_id: exports_external.string().optional(),
|
|
32324
|
+
session_id: exports_external.string().optional().optional(),
|
|
31857
32325
|
project_id: exports_external.string().optional(),
|
|
31858
32326
|
working_dir: exports_external.string().optional()
|
|
31859
32327
|
}, async ({ name, description, session_id, project_id, working_dir }) => {
|
|
@@ -31894,9 +32362,10 @@ server.tool("browser_project_list", "List all registered projects", {}, async ()
|
|
|
31894
32362
|
return err(e);
|
|
31895
32363
|
}
|
|
31896
32364
|
});
|
|
31897
|
-
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 }) => {
|
|
32365
|
+
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 }) => {
|
|
31898
32366
|
try {
|
|
31899
|
-
const
|
|
32367
|
+
const sid = resolveSessionId(session_id);
|
|
32368
|
+
const page = getSessionPage(sid);
|
|
31900
32369
|
await scroll(page, direction, amount);
|
|
31901
32370
|
await new Promise((r) => setTimeout(r, wait_ms));
|
|
31902
32371
|
const result = await takeScreenshot(page, { maxWidth: 1280, track: true });
|
|
@@ -31911,9 +32380,10 @@ server.tool("browser_scroll_and_screenshot", "Scroll the page and take a screens
|
|
|
31911
32380
|
return err(e);
|
|
31912
32381
|
}
|
|
31913
32382
|
});
|
|
31914
|
-
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 }) => {
|
|
32383
|
+
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 }) => {
|
|
31915
32384
|
try {
|
|
31916
|
-
const
|
|
32385
|
+
const sid = resolveSessionId(session_id);
|
|
32386
|
+
const page = getSessionPage(sid);
|
|
31917
32387
|
const start = Date.now();
|
|
31918
32388
|
if (url_pattern) {
|
|
31919
32389
|
await page.waitForURL(url_pattern, { timeout });
|
|
@@ -31935,16 +32405,63 @@ server.tool("browser_session_get_by_name", "Get a session by its name", { name:
|
|
|
31935
32405
|
return err(e);
|
|
31936
32406
|
}
|
|
31937
32407
|
});
|
|
31938
|
-
server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
32408
|
+
server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
31939
32409
|
try {
|
|
31940
|
-
|
|
32410
|
+
const sid = resolveSessionId(session_id);
|
|
32411
|
+
return json({ session: renameSession2(sid, name) });
|
|
31941
32412
|
} catch (e) {
|
|
31942
32413
|
return err(e);
|
|
31943
32414
|
}
|
|
31944
32415
|
});
|
|
31945
|
-
server.tool("
|
|
32416
|
+
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 }) => {
|
|
31946
32417
|
try {
|
|
31947
|
-
const
|
|
32418
|
+
const sid = resolveSessionId(session_id);
|
|
32419
|
+
const { lockSession: lockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
32420
|
+
return json({ session: lockSession2(sid, agent_id) });
|
|
32421
|
+
} catch (e) {
|
|
32422
|
+
return err(e);
|
|
32423
|
+
}
|
|
32424
|
+
});
|
|
32425
|
+
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 }) => {
|
|
32426
|
+
try {
|
|
32427
|
+
const sid = resolveSessionId(session_id);
|
|
32428
|
+
const { unlockSession: unlockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
32429
|
+
return json({ session: unlockSession2(sid, agent_id) });
|
|
32430
|
+
} catch (e) {
|
|
32431
|
+
return err(e);
|
|
32432
|
+
}
|
|
32433
|
+
});
|
|
32434
|
+
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 }) => {
|
|
32435
|
+
try {
|
|
32436
|
+
const sid = resolveSessionId(session_id);
|
|
32437
|
+
const { transferSession: transferSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
32438
|
+
return json({ session: transferSession2(sid, to_agent_id) });
|
|
32439
|
+
} catch (e) {
|
|
32440
|
+
return err(e);
|
|
32441
|
+
}
|
|
32442
|
+
});
|
|
32443
|
+
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 }) => {
|
|
32444
|
+
try {
|
|
32445
|
+
const sid = resolveSessionId(session_id);
|
|
32446
|
+
const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
32447
|
+
return json({ tags: addSessionTag2(sid, tag) });
|
|
32448
|
+
} catch (e) {
|
|
32449
|
+
return err(e);
|
|
32450
|
+
}
|
|
32451
|
+
});
|
|
32452
|
+
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 }) => {
|
|
32453
|
+
try {
|
|
32454
|
+
const sid = resolveSessionId(session_id);
|
|
32455
|
+
const { removeSessionTag: removeSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
32456
|
+
return json({ tags: removeSessionTag2(sid, tag) });
|
|
32457
|
+
} catch (e) {
|
|
32458
|
+
return err(e);
|
|
32459
|
+
}
|
|
32460
|
+
});
|
|
32461
|
+
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 }) => {
|
|
32462
|
+
try {
|
|
32463
|
+
const sid = resolveSessionId(session_id);
|
|
32464
|
+
const page = getSessionPage(sid);
|
|
31948
32465
|
await clickText(page, text, { exact, timeout });
|
|
31949
32466
|
return json({ clicked: text });
|
|
31950
32467
|
} catch (e) {
|
|
@@ -31952,21 +32469,23 @@ server.tool("browser_click_text", "Click an element by its visible text content"
|
|
|
31952
32469
|
}
|
|
31953
32470
|
});
|
|
31954
32471
|
server.tool("browser_fill_form", "Fill multiple form fields in one call. Fields map: { selector: value }. Handles text, checkboxes, selects.", {
|
|
31955
|
-
session_id: exports_external.string(),
|
|
32472
|
+
session_id: exports_external.string().optional(),
|
|
31956
32473
|
fields: exports_external.record(exports_external.union([exports_external.string(), exports_external.boolean()])),
|
|
31957
32474
|
submit_selector: exports_external.string().optional()
|
|
31958
32475
|
}, async ({ session_id, fields, submit_selector }) => {
|
|
31959
32476
|
try {
|
|
31960
|
-
const
|
|
32477
|
+
const sid = resolveSessionId(session_id);
|
|
32478
|
+
const page = getSessionPage(sid);
|
|
31961
32479
|
const result = await fillForm(page, fields, submit_selector);
|
|
31962
32480
|
return json(result);
|
|
31963
32481
|
} catch (e) {
|
|
31964
|
-
return
|
|
32482
|
+
return errWithScreenshot(e, session_id);
|
|
31965
32483
|
}
|
|
31966
32484
|
});
|
|
31967
|
-
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 }) => {
|
|
32485
|
+
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 }) => {
|
|
31968
32486
|
try {
|
|
31969
|
-
const
|
|
32487
|
+
const sid = resolveSessionId(session_id);
|
|
32488
|
+
const page = getSessionPage(sid);
|
|
31970
32489
|
const start = Date.now();
|
|
31971
32490
|
await waitForText(page, text, { timeout, exact });
|
|
31972
32491
|
return json({ found: true, elapsed_ms: Date.now() - start });
|
|
@@ -31974,46 +32493,51 @@ server.tool("browser_wait_for_text", "Wait until specific text appears on the pa
|
|
|
31974
32493
|
return err(e);
|
|
31975
32494
|
}
|
|
31976
32495
|
});
|
|
31977
|
-
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 }) => {
|
|
32496
|
+
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 }) => {
|
|
31978
32497
|
try {
|
|
31979
|
-
const
|
|
32498
|
+
const sid = resolveSessionId(session_id);
|
|
32499
|
+
const page = getSessionPage(sid);
|
|
31980
32500
|
return json(await elementExists(page, selector, { visible: check_visible }));
|
|
31981
32501
|
} catch (e) {
|
|
31982
32502
|
return err(e);
|
|
31983
32503
|
}
|
|
31984
32504
|
});
|
|
31985
|
-
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 }) => {
|
|
32505
|
+
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 }) => {
|
|
31986
32506
|
try {
|
|
31987
|
-
const
|
|
32507
|
+
const sid = resolveSessionId(session_id);
|
|
32508
|
+
const page = getSessionPage(sid);
|
|
31988
32509
|
const info = await getPageInfo(page);
|
|
31989
|
-
const errors2 = getConsoleLog(
|
|
32510
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
31990
32511
|
info.has_console_errors = errors2.length > 0;
|
|
31991
32512
|
return json(info);
|
|
31992
32513
|
} catch (e) {
|
|
31993
32514
|
return err(e);
|
|
31994
32515
|
}
|
|
31995
32516
|
});
|
|
31996
|
-
server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32517
|
+
server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
31997
32518
|
try {
|
|
31998
|
-
const
|
|
32519
|
+
const sid = resolveSessionId(session_id);
|
|
32520
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
31999
32521
|
return json({ has_errors: errors2.length > 0, error_count: errors2.length, errors: errors2 });
|
|
32000
32522
|
} catch (e) {
|
|
32001
32523
|
return err(e);
|
|
32002
32524
|
}
|
|
32003
32525
|
});
|
|
32004
|
-
server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32526
|
+
server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
32005
32527
|
try {
|
|
32528
|
+
const sid = resolveSessionId(session_id);
|
|
32006
32529
|
const { clearConsoleLog: clearConsoleLog2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
32007
|
-
clearConsoleLog2(
|
|
32530
|
+
clearConsoleLog2(sid);
|
|
32008
32531
|
return json({ cleared: true });
|
|
32009
32532
|
} catch (e) {
|
|
32010
32533
|
return err(e);
|
|
32011
32534
|
}
|
|
32012
32535
|
});
|
|
32013
32536
|
var activeWatchHandles2 = new Map;
|
|
32014
|
-
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 }) => {
|
|
32537
|
+
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 }) => {
|
|
32015
32538
|
try {
|
|
32016
|
-
const
|
|
32539
|
+
const sid = resolveSessionId(session_id);
|
|
32540
|
+
const page = getSessionPage(sid);
|
|
32017
32541
|
const handle = watchPage(page, { selector, intervalMs: interval_ms, maxChanges: max_changes });
|
|
32018
32542
|
activeWatchHandles2.set(handle.id, handle);
|
|
32019
32543
|
return json({ watch_id: handle.id });
|
|
@@ -32040,7 +32564,7 @@ server.tool("browser_watch_stop", "Stop a DOM change watcher", { watch_id: expor
|
|
|
32040
32564
|
});
|
|
32041
32565
|
server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
|
|
32042
32566
|
project_id: exports_external.string().optional(),
|
|
32043
|
-
session_id: exports_external.string().optional(),
|
|
32567
|
+
session_id: exports_external.string().optional().optional(),
|
|
32044
32568
|
tag: exports_external.string().optional(),
|
|
32045
32569
|
is_favorite: exports_external.boolean().optional(),
|
|
32046
32570
|
date_from: exports_external.string().optional(),
|
|
@@ -32128,14 +32652,14 @@ server.tool("browser_gallery_diff", "Pixel-diff two gallery screenshots. Returns
|
|
|
32128
32652
|
return err(e);
|
|
32129
32653
|
}
|
|
32130
32654
|
});
|
|
32131
|
-
server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
32655
|
+
server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional().optional() }, async ({ session_id }) => {
|
|
32132
32656
|
try {
|
|
32133
32657
|
return json({ downloads: listDownloads(session_id), count: listDownloads(session_id).length });
|
|
32134
32658
|
} catch (e) {
|
|
32135
32659
|
return err(e);
|
|
32136
32660
|
}
|
|
32137
32661
|
});
|
|
32138
|
-
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 }) => {
|
|
32662
|
+
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 }) => {
|
|
32139
32663
|
try {
|
|
32140
32664
|
const file = getDownload(id, session_id);
|
|
32141
32665
|
if (!file)
|
|
@@ -32146,7 +32670,7 @@ server.tool("browser_downloads_get", "Get a downloaded file by id, returning bas
|
|
|
32146
32670
|
return err(e);
|
|
32147
32671
|
}
|
|
32148
32672
|
});
|
|
32149
|
-
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 }) => {
|
|
32673
|
+
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 }) => {
|
|
32150
32674
|
try {
|
|
32151
32675
|
const deleted = deleteDownload(id, session_id);
|
|
32152
32676
|
return json({ deleted });
|
|
@@ -32161,7 +32685,7 @@ server.tool("browser_downloads_clean", "Delete all downloaded files older than N
|
|
|
32161
32685
|
return err(e);
|
|
32162
32686
|
}
|
|
32163
32687
|
});
|
|
32164
|
-
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 }) => {
|
|
32688
|
+
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 }) => {
|
|
32165
32689
|
try {
|
|
32166
32690
|
const finalPath = exportToPath(id, target_path, session_id);
|
|
32167
32691
|
return json({ path: finalPath });
|
|
@@ -32186,12 +32710,13 @@ server.tool("browser_persist_file", "Persist a file permanently via open-files S
|
|
|
32186
32710
|
return err(e);
|
|
32187
32711
|
}
|
|
32188
32712
|
});
|
|
32189
|
-
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 }) => {
|
|
32713
|
+
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 }) => {
|
|
32190
32714
|
try {
|
|
32191
|
-
const
|
|
32192
|
-
const
|
|
32193
|
-
const
|
|
32194
|
-
|
|
32715
|
+
const sid = resolveSessionId(session_id);
|
|
32716
|
+
const page = getSessionPage(sid);
|
|
32717
|
+
const before = getLastSnapshot(sid);
|
|
32718
|
+
const after = await takeSnapshot(page, sid);
|
|
32719
|
+
setLastSnapshot(sid, after);
|
|
32195
32720
|
if (!before) {
|
|
32196
32721
|
return json({
|
|
32197
32722
|
message: "No previous snapshot \u2014 returning current snapshot only.",
|
|
@@ -32214,12 +32739,13 @@ server.tool("browser_snapshot_diff", "Take a new accessibility snapshot and diff
|
|
|
32214
32739
|
return err(e);
|
|
32215
32740
|
}
|
|
32216
32741
|
});
|
|
32217
|
-
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 }) => {
|
|
32742
|
+
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 }) => {
|
|
32218
32743
|
try {
|
|
32219
|
-
const
|
|
32220
|
-
const
|
|
32221
|
-
const
|
|
32222
|
-
const
|
|
32744
|
+
const sid = resolveSessionId(session_id);
|
|
32745
|
+
const session = getSession2(sid);
|
|
32746
|
+
const networkLog = getNetworkLog(sid);
|
|
32747
|
+
const consoleLog = getConsoleLog(sid);
|
|
32748
|
+
const galleryEntries = listEntries({ sessionId: sid, limit: 1000 });
|
|
32223
32749
|
let totalChars = 0;
|
|
32224
32750
|
for (const req of networkLog) {
|
|
32225
32751
|
totalChars += (req.url?.length ?? 0) + (req.request_headers?.length ?? 0) + (req.response_headers?.length ?? 0) + (req.request_body?.length ?? 0);
|
|
@@ -32231,7 +32757,7 @@ server.tool("browser_session_stats", "Get session info and estimated token usage
|
|
|
32231
32757
|
totalChars += (entry.url?.length ?? 0) + (entry.title?.length ?? 0) + (entry.notes?.length ?? 0) + (entry.tags?.join(",").length ?? 0);
|
|
32232
32758
|
}
|
|
32233
32759
|
const estimatedTokens = Math.ceil(totalChars / 4);
|
|
32234
|
-
const tokenBudget = getTokenBudget(
|
|
32760
|
+
const tokenBudget = getTokenBudget(sid);
|
|
32235
32761
|
return json({
|
|
32236
32762
|
session,
|
|
32237
32763
|
network_request_count: networkLog.length,
|
|
@@ -32245,52 +32771,57 @@ server.tool("browser_session_stats", "Get session info and estimated token usage
|
|
|
32245
32771
|
return err(e);
|
|
32246
32772
|
}
|
|
32247
32773
|
});
|
|
32248
|
-
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 }) => {
|
|
32774
|
+
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 }) => {
|
|
32249
32775
|
try {
|
|
32250
|
-
const
|
|
32776
|
+
const sid = resolveSessionId(session_id);
|
|
32777
|
+
const page = getSessionPage(sid);
|
|
32251
32778
|
const tab = await newTab(page, url);
|
|
32252
32779
|
return json(tab);
|
|
32253
32780
|
} catch (e) {
|
|
32254
32781
|
return err(e);
|
|
32255
32782
|
}
|
|
32256
32783
|
});
|
|
32257
|
-
server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32784
|
+
server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
32258
32785
|
try {
|
|
32259
|
-
const
|
|
32786
|
+
const sid = resolveSessionId(session_id);
|
|
32787
|
+
const page = getSessionPage(sid);
|
|
32260
32788
|
const tabs = await listTabs(page);
|
|
32261
32789
|
return json({ tabs, count: tabs.length });
|
|
32262
32790
|
} catch (e) {
|
|
32263
32791
|
return err(e);
|
|
32264
32792
|
}
|
|
32265
32793
|
});
|
|
32266
|
-
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 }) => {
|
|
32794
|
+
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 }) => {
|
|
32267
32795
|
try {
|
|
32268
|
-
const
|
|
32796
|
+
const sid = resolveSessionId(session_id);
|
|
32797
|
+
const page = getSessionPage(sid);
|
|
32269
32798
|
const result = await switchTab(page, tab_id);
|
|
32270
|
-
setSessionPage(
|
|
32799
|
+
setSessionPage(sid, result.page);
|
|
32271
32800
|
return json(result.tab);
|
|
32272
32801
|
} catch (e) {
|
|
32273
32802
|
return err(e);
|
|
32274
32803
|
}
|
|
32275
32804
|
});
|
|
32276
|
-
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 }) => {
|
|
32805
|
+
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 }) => {
|
|
32277
32806
|
try {
|
|
32278
|
-
const
|
|
32807
|
+
const sid = resolveSessionId(session_id);
|
|
32808
|
+
const page = getSessionPage(sid);
|
|
32279
32809
|
const context = page.context();
|
|
32280
32810
|
const result = await closeTab(page, tab_id);
|
|
32281
32811
|
const remainingPages = context.pages();
|
|
32282
32812
|
const newActivePage = remainingPages[result.active_tab.index];
|
|
32283
32813
|
if (newActivePage) {
|
|
32284
|
-
setSessionPage(
|
|
32814
|
+
setSessionPage(sid, newActivePage);
|
|
32285
32815
|
}
|
|
32286
32816
|
return json(result);
|
|
32287
32817
|
} catch (e) {
|
|
32288
32818
|
return err(e);
|
|
32289
32819
|
}
|
|
32290
32820
|
});
|
|
32291
|
-
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 }) => {
|
|
32821
|
+
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 }) => {
|
|
32292
32822
|
try {
|
|
32293
|
-
const
|
|
32823
|
+
const sid = resolveSessionId(session_id);
|
|
32824
|
+
const result = await handleDialog(sid, action, prompt_text);
|
|
32294
32825
|
if (!result.handled)
|
|
32295
32826
|
return err(new Error("No pending dialogs for this session"));
|
|
32296
32827
|
return json(result);
|
|
@@ -32298,28 +32829,31 @@ server.tool("browser_handle_dialog", "Accept or dismiss a pending dialog (alert,
|
|
|
32298
32829
|
return err(e);
|
|
32299
32830
|
}
|
|
32300
32831
|
});
|
|
32301
|
-
server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
32832
|
+
server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
32302
32833
|
try {
|
|
32303
|
-
const
|
|
32834
|
+
const sid = resolveSessionId(session_id);
|
|
32835
|
+
const dialogs = getDialogs(sid);
|
|
32304
32836
|
return json({ dialogs, count: dialogs.length });
|
|
32305
32837
|
} catch (e) {
|
|
32306
32838
|
return err(e);
|
|
32307
32839
|
}
|
|
32308
32840
|
});
|
|
32309
|
-
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 }) => {
|
|
32841
|
+
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 }) => {
|
|
32310
32842
|
try {
|
|
32311
|
-
const
|
|
32843
|
+
const sid = resolveSessionId(session_id);
|
|
32844
|
+
const page = getSessionPage(sid);
|
|
32312
32845
|
const info = await saveProfile(page, name);
|
|
32313
32846
|
return json(info);
|
|
32314
32847
|
} catch (e) {
|
|
32315
32848
|
return err(e);
|
|
32316
32849
|
}
|
|
32317
32850
|
});
|
|
32318
|
-
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 }) => {
|
|
32851
|
+
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 }) => {
|
|
32319
32852
|
try {
|
|
32320
32853
|
const profileData = loadProfile(name);
|
|
32321
32854
|
if (session_id) {
|
|
32322
|
-
const
|
|
32855
|
+
const sid = resolveSessionId(session_id);
|
|
32856
|
+
const page = getSessionPage(sid);
|
|
32323
32857
|
const applied = await applyProfile(page, profileData);
|
|
32324
32858
|
return json({ ...applied, profile: name });
|
|
32325
32859
|
}
|
|
@@ -32457,7 +32991,13 @@ server.tool("browser_help", "Show all available browser tools grouped by categor
|
|
|
32457
32991
|
{ tool: "browser_session_close", description: "Close a session" },
|
|
32458
32992
|
{ tool: "browser_session_get_by_name", description: "Get session by name" },
|
|
32459
32993
|
{ tool: "browser_session_rename", description: "Rename a session" },
|
|
32994
|
+
{ tool: "browser_session_lock", description: "Lock a session for an agent" },
|
|
32995
|
+
{ tool: "browser_session_unlock", description: "Unlock a session" },
|
|
32996
|
+
{ tool: "browser_session_transfer", description: "Transfer session to another agent" },
|
|
32997
|
+
{ tool: "browser_session_tag", description: "Add a tag to a session" },
|
|
32998
|
+
{ tool: "browser_session_untag", description: "Remove a tag from a session" },
|
|
32460
32999
|
{ tool: "browser_session_stats", description: "Get session stats and token usage" },
|
|
33000
|
+
{ tool: "browser_session_timeline", description: "Get chronological action log" },
|
|
32461
33001
|
{ tool: "browser_tab_new", description: "Open a new tab" },
|
|
32462
33002
|
{ tool: "browser_tab_list", description: "List all open tabs" },
|
|
32463
33003
|
{ tool: "browser_tab_switch", description: "Switch to a tab by index" },
|
|
@@ -32495,18 +33035,19 @@ server.tool("browser_version", "Get the running browser MCP version, tool count,
|
|
|
32495
33035
|
}
|
|
32496
33036
|
});
|
|
32497
33037
|
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.", {
|
|
32498
|
-
session_id: exports_external.string(),
|
|
33038
|
+
session_id: exports_external.string().optional(),
|
|
32499
33039
|
selector: exports_external.string().optional(),
|
|
32500
33040
|
ref: exports_external.string().optional(),
|
|
32501
33041
|
screenshot: exports_external.boolean().optional().default(true),
|
|
32502
33042
|
wait_ms: exports_external.number().optional().default(200)
|
|
32503
33043
|
}, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
|
|
32504
33044
|
try {
|
|
32505
|
-
const
|
|
33045
|
+
const sid = resolveSessionId(session_id);
|
|
33046
|
+
const page = getSessionPage(sid);
|
|
32506
33047
|
let locator;
|
|
32507
33048
|
if (ref) {
|
|
32508
33049
|
const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
32509
|
-
locator = getRefLocator2(page,
|
|
33050
|
+
locator = getRefLocator2(page, sid, ref);
|
|
32510
33051
|
} else if (selector) {
|
|
32511
33052
|
locator = page.locator(selector).first();
|
|
32512
33053
|
} else {
|
|
@@ -32531,11 +33072,12 @@ server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or
|
|
|
32531
33072
|
return err(e);
|
|
32532
33073
|
}
|
|
32533
33074
|
});
|
|
32534
|
-
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 }) => {
|
|
33075
|
+
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 }) => {
|
|
32535
33076
|
try {
|
|
32536
|
-
const
|
|
33077
|
+
const sid = resolveSessionId(session_id);
|
|
33078
|
+
const page = getSessionPage(sid);
|
|
32537
33079
|
const info = await getPageInfo(page);
|
|
32538
|
-
const errors2 = getConsoleLog(
|
|
33080
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
32539
33081
|
info.has_console_errors = errors2.length > 0;
|
|
32540
33082
|
let perf = {};
|
|
32541
33083
|
try {
|
|
@@ -32549,8 +33091,8 @@ server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u20
|
|
|
32549
33091
|
let snapshot_refs = "";
|
|
32550
33092
|
let interactive_count = 0;
|
|
32551
33093
|
try {
|
|
32552
|
-
const snap = await takeSnapshot(page,
|
|
32553
|
-
setLastSnapshot(
|
|
33094
|
+
const snap = await takeSnapshot(page, sid);
|
|
33095
|
+
setLastSnapshot(sid, snap);
|
|
32554
33096
|
interactive_count = snap.interactive_count;
|
|
32555
33097
|
snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
32556
33098
|
} catch {}
|
|
@@ -32559,9 +33101,10 @@ server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u20
|
|
|
32559
33101
|
return err(e);
|
|
32560
33102
|
}
|
|
32561
33103
|
});
|
|
32562
|
-
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 }) => {
|
|
33104
|
+
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 }) => {
|
|
32563
33105
|
try {
|
|
32564
|
-
const
|
|
33106
|
+
const sid = resolveSessionId(session_id);
|
|
33107
|
+
const page = getSessionPage(sid);
|
|
32565
33108
|
const { getCredentials: getCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
|
|
32566
33109
|
const creds = await getCredentials2(service);
|
|
32567
33110
|
if (!creds)
|
|
@@ -32575,9 +33118,10 @@ server.tool("browser_secrets_login", "Login to a service using credentials from
|
|
|
32575
33118
|
return err(e);
|
|
32576
33119
|
}
|
|
32577
33120
|
});
|
|
32578
|
-
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 }) => {
|
|
33121
|
+
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 }) => {
|
|
32579
33122
|
try {
|
|
32580
|
-
const
|
|
33123
|
+
const sid = resolveSessionId(session_id);
|
|
33124
|
+
const page = getSessionPage(sid);
|
|
32581
33125
|
const { rememberPage: rememberPage2 } = await Promise.resolve().then(() => (init_page_memory(), exports_page_memory));
|
|
32582
33126
|
const url = page.url();
|
|
32583
33127
|
await rememberPage2(url, facts, tags);
|
|
@@ -32595,12 +33139,13 @@ server.tool("browser_recall", "Retrieve cached page facts from open-mementos. Re
|
|
|
32595
33139
|
return err(e);
|
|
32596
33140
|
}
|
|
32597
33141
|
});
|
|
32598
|
-
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 }) => {
|
|
33142
|
+
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 }) => {
|
|
32599
33143
|
try {
|
|
32600
|
-
const
|
|
33144
|
+
const sid = resolveSessionId(session_id);
|
|
33145
|
+
const page = getSessionPage(sid);
|
|
32601
33146
|
const { announceNavigation: announceNavigation2 } = await Promise.resolve().then(() => (init_coordination(), exports_coordination));
|
|
32602
33147
|
const url = page.url();
|
|
32603
|
-
await announceNavigation2(url,
|
|
33148
|
+
await announceNavigation2(url, sid);
|
|
32604
33149
|
return json({ announced: true, url, message });
|
|
32605
33150
|
} catch (e) {
|
|
32606
33151
|
return err(e);
|
|
@@ -32640,9 +33185,10 @@ server.tool("browser_task_complete", "Mark a browser task as completed with extr
|
|
|
32640
33185
|
return err(e);
|
|
32641
33186
|
}
|
|
32642
33187
|
});
|
|
32643
|
-
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 }) => {
|
|
33188
|
+
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 }) => {
|
|
32644
33189
|
try {
|
|
32645
|
-
const
|
|
33190
|
+
const sid = resolveSessionId(session_id);
|
|
33191
|
+
const page = getSessionPage(sid);
|
|
32646
33192
|
const { runBrowserSkill: runBrowserSkill2 } = await Promise.resolve().then(() => (init_skills_runner(), exports_skills_runner));
|
|
32647
33193
|
return json(await runBrowserSkill2(skill, params, page));
|
|
32648
33194
|
} catch (e) {
|
|
@@ -32658,7 +33204,7 @@ server.tool("browser_skill_list", "List available browser skills.", {}, async ()
|
|
|
32658
33204
|
}
|
|
32659
33205
|
});
|
|
32660
33206
|
server.tool("browser_batch", "Execute multiple browser actions in one call. Returns final snapshot. Eliminates 80% of round trips for multi-step flows.", {
|
|
32661
|
-
session_id: exports_external.string(),
|
|
33207
|
+
session_id: exports_external.string().optional(),
|
|
32662
33208
|
actions: exports_external.array(exports_external.object({
|
|
32663
33209
|
tool: exports_external.string(),
|
|
32664
33210
|
args: exports_external.record(exports_external.unknown()).optional().default({})
|
|
@@ -32666,12 +33212,13 @@ server.tool("browser_batch", "Execute multiple browser actions in one call. Retu
|
|
|
32666
33212
|
}, async ({ session_id, actions }) => {
|
|
32667
33213
|
try {
|
|
32668
33214
|
const results = [];
|
|
32669
|
-
const
|
|
33215
|
+
const sid = resolveSessionId(session_id);
|
|
33216
|
+
const page = getSessionPage(sid);
|
|
32670
33217
|
const t0 = Date.now();
|
|
32671
33218
|
for (const action of actions) {
|
|
32672
33219
|
try {
|
|
32673
33220
|
const toolName = action.tool.replace(/^browser_/, "");
|
|
32674
|
-
const args = { session_id, ...action.args };
|
|
33221
|
+
const args = { session_id: sid, ...action.args };
|
|
32675
33222
|
switch (toolName) {
|
|
32676
33223
|
case "navigate":
|
|
32677
33224
|
await navigate(page, action.args.url);
|
|
@@ -32680,7 +33227,7 @@ server.tool("browser_batch", "Execute multiple browser actions in one call. Retu
|
|
|
32680
33227
|
case "click":
|
|
32681
33228
|
if (args.ref) {
|
|
32682
33229
|
const { clickRef: clickRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
32683
|
-
await clickRef2(page,
|
|
33230
|
+
await clickRef2(page, sid, args.ref);
|
|
32684
33231
|
} else if (args.selector)
|
|
32685
33232
|
await page.click(args.selector);
|
|
32686
33233
|
results.push({ tool: action.tool, success: true });
|
|
@@ -32688,7 +33235,7 @@ server.tool("browser_batch", "Execute multiple browser actions in one call. Retu
|
|
|
32688
33235
|
case "type":
|
|
32689
33236
|
if (args.ref && args.text) {
|
|
32690
33237
|
const { typeRef: typeRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
32691
|
-
await typeRef2(page,
|
|
33238
|
+
await typeRef2(page, sid, args.ref, args.text);
|
|
32692
33239
|
} else if (args.selector && args.text)
|
|
32693
33240
|
await page.fill(args.selector, args.text);
|
|
32694
33241
|
results.push({ tool: action.tool, success: true });
|
|
@@ -32728,7 +33275,7 @@ server.tool("browser_batch", "Execute multiple browser actions in one call. Retu
|
|
|
32728
33275
|
}
|
|
32729
33276
|
let final_snapshot = {};
|
|
32730
33277
|
try {
|
|
32731
|
-
const snap = await takeSnapshot(page,
|
|
33278
|
+
const snap = await takeSnapshot(page, sid);
|
|
32732
33279
|
final_snapshot = {
|
|
32733
33280
|
refs: Object.fromEntries(Object.entries(snap.refs).slice(0, 20)),
|
|
32734
33281
|
interactive_count: snap.interactive_count
|
|
@@ -32825,18 +33372,20 @@ server.tool("browser_watch_delete", "Delete a URL watcher.", { watch_id: exports
|
|
|
32825
33372
|
return err(e);
|
|
32826
33373
|
}
|
|
32827
33374
|
});
|
|
32828
|
-
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 }) => {
|
|
33375
|
+
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 }) => {
|
|
32829
33376
|
try {
|
|
32830
|
-
const
|
|
33377
|
+
const sid = resolveSessionId(session_id);
|
|
33378
|
+
const page = getSessionPage(sid);
|
|
32831
33379
|
const { executeBrowserTask: executeBrowserTask2 } = await Promise.resolve().then(() => (init_ai_task(), exports_ai_task));
|
|
32832
|
-
return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId:
|
|
33380
|
+
return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId: sid }));
|
|
32833
33381
|
} catch (e) {
|
|
32834
33382
|
return err(e);
|
|
32835
33383
|
}
|
|
32836
33384
|
});
|
|
32837
|
-
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 }) => {
|
|
33385
|
+
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 }) => {
|
|
32838
33386
|
try {
|
|
32839
|
-
const
|
|
33387
|
+
const sid = resolveSessionId(session_id);
|
|
33388
|
+
const page = getSessionPage(sid);
|
|
32840
33389
|
const checks = [];
|
|
32841
33390
|
let passed = true;
|
|
32842
33391
|
for (const part of condition.split(/\s+AND\s+/i)) {
|