@agenticmail/enterprise 0.5.237 → 0.5.239

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.
@@ -1180,7 +1180,92 @@ export function createAgentRoutes(opts: {
1180
1180
  }
1181
1181
 
1182
1182
  const { execSync, spawn } = await import('node:child_process');
1183
- const chromePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || '/usr/bin/chromium';
1183
+ const { existsSync, mkdirSync, writeFileSync } = await import('node:fs');
1184
+ const { join, dirname } = await import('node:path');
1185
+ const { homedir } = await import('node:os');
1186
+
1187
+ // ── Auto-detect Chrome/Chromium across all platforms ──
1188
+ const chromeCandidates = [
1189
+ process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
1190
+ // macOS
1191
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
1192
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
1193
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
1194
+ // Linux
1195
+ '/usr/bin/google-chrome',
1196
+ '/usr/bin/google-chrome-stable',
1197
+ '/usr/bin/chromium',
1198
+ '/usr/bin/chromium-browser',
1199
+ '/snap/bin/chromium',
1200
+ '/usr/local/bin/chromium',
1201
+ // Windows
1202
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
1203
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
1204
+ // Playwright bundled (check common install locations)
1205
+ join(homedir(), '.cache', 'ms-playwright', 'chromium-*', 'chrome-linux', 'chrome'),
1206
+ join(homedir(), '.cache', 'ms-playwright', 'chromium-*', 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
1207
+ ].filter(Boolean) as string[];
1208
+
1209
+ let chromePath = '';
1210
+ for (const candidate of chromeCandidates) {
1211
+ if (candidate.includes('*')) {
1212
+ // Glob pattern — resolve with fs
1213
+ try {
1214
+ const { globSync } = await import('node:fs');
1215
+ const matches = (globSync as any)(candidate);
1216
+ if (matches?.length && existsSync(matches[0])) { chromePath = matches[0]; break; }
1217
+ } catch {
1218
+ // globSync not available in older Node, try manual resolve
1219
+ try {
1220
+ const parentDir = dirname(candidate.split('*')[0]);
1221
+ if (existsSync(parentDir)) {
1222
+ const { readdirSync } = await import('node:fs');
1223
+ const dirs = readdirSync(parentDir).filter((d: string) => d.startsWith('chromium-')).sort().reverse();
1224
+ for (const d of dirs) {
1225
+ const suffix = candidate.split('*')[1];
1226
+ const resolved = join(parentDir, d, suffix);
1227
+ if (existsSync(resolved)) { chromePath = resolved; break; }
1228
+ }
1229
+ if (chromePath) break;
1230
+ }
1231
+ } catch { /* skip */ }
1232
+ }
1233
+ } else if (existsSync(candidate)) {
1234
+ chromePath = candidate;
1235
+ break;
1236
+ }
1237
+ }
1238
+
1239
+ // If no Chrome found, try to install Playwright Chromium automatically
1240
+ if (!chromePath) {
1241
+ try {
1242
+ console.log('[meeting-browser] No Chrome/Chromium found — installing Playwright Chromium...');
1243
+ execSync('npx playwright install chromium 2>&1', { timeout: 120_000, stdio: 'pipe' });
1244
+ // Re-check for Playwright Chromium
1245
+ const pwCacheDir = join(homedir(), '.cache', 'ms-playwright');
1246
+ if (existsSync(pwCacheDir)) {
1247
+ const { readdirSync } = await import('node:fs');
1248
+ const chromiumDirs = readdirSync(pwCacheDir).filter((d: string) => d.startsWith('chromium-')).sort().reverse();
1249
+ for (const d of chromiumDirs) {
1250
+ // Try Linux path
1251
+ const linuxPath = join(pwCacheDir, d, 'chrome-linux', 'chrome');
1252
+ if (existsSync(linuxPath)) { chromePath = linuxPath; break; }
1253
+ // Try macOS path
1254
+ const macPath = join(pwCacheDir, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
1255
+ if (existsSync(macPath)) { chromePath = macPath; break; }
1256
+ }
1257
+ }
1258
+ } catch (installErr: any) {
1259
+ console.error('[meeting-browser] Failed to auto-install Chromium:', installErr.message);
1260
+ }
1261
+ }
1262
+
1263
+ if (!chromePath) {
1264
+ return c.json({
1265
+ error: 'No Chrome or Chromium browser found on this machine. Install Google Chrome or run: npx playwright install chromium',
1266
+ hint: 'On macOS: brew install --cask google-chrome | On Linux: apt install chromium-browser | On Windows: download from google.com/chrome',
1267
+ }, 400);
1268
+ }
1184
1269
 
1185
1270
  // Check if a meeting browser is already running for this agent
1186
1271
  const existingPort = (managed.config as any)?.meetingBrowserPort;
@@ -1194,6 +1279,52 @@ export function createAgentRoutes(opts: {
1194
1279
  } catch { /* not running, will launch new one */ }
1195
1280
  }
1196
1281
 
1282
+ // ── Create a realistic browser profile using agent identity ──
1283
+ const agentName = managed.displayName || managed.display_name || managed.name || (managed.config as any)?.displayName || (managed.config as any)?.name || 'Agent';
1284
+ const agentRole = (managed.config as any)?.role || (managed.config as any)?.description || 'AI Assistant';
1285
+ const profileDir = join(homedir(), '.agenticmail', 'browser-profiles', agentId);
1286
+ mkdirSync(profileDir, { recursive: true });
1287
+
1288
+ // Write Chrome preferences to make the browser look like a real user
1289
+ const prefsDir = join(profileDir, 'Default');
1290
+ mkdirSync(prefsDir, { recursive: true });
1291
+ const prefsFile = join(prefsDir, 'Preferences');
1292
+ if (!existsSync(prefsFile)) {
1293
+ const prefs = {
1294
+ profile: {
1295
+ name: agentName,
1296
+ avatar_index: Math.floor(Math.random() * 28), // Chrome has 28 avatar options
1297
+ managed_user_id: '',
1298
+ is_using_default_name: false,
1299
+ is_using_default_avatar: false,
1300
+ },
1301
+ browser: {
1302
+ has_seen_welcome_page: true,
1303
+ check_default_browser: false,
1304
+ },
1305
+ distribution: {
1306
+ import_bookmarks: false,
1307
+ import_history: false,
1308
+ import_search_engine: false,
1309
+ suppress_first_run_bubble: true,
1310
+ suppress_first_run_default_browser_prompt: true,
1311
+ skip_first_run_ui: true,
1312
+ make_chrome_default_for_user: false,
1313
+ },
1314
+ session: { restore_on_startup: 1 },
1315
+ search: { suggest_enabled: true },
1316
+ translate: { enabled: false },
1317
+ net: { network_prediction_options: 2 }, // don't prefetch
1318
+ webkit: { webprefs: { default_font_size: 16 } },
1319
+ download: { prompt_for_download: true, default_directory: join(profileDir, 'Downloads') },
1320
+ savefile: { default_directory: join(profileDir, 'Downloads') },
1321
+ credentials_enable_service: false,
1322
+ credentials_enable_autosign_in: false,
1323
+ };
1324
+ mkdirSync(join(profileDir, 'Downloads'), { recursive: true });
1325
+ writeFileSync(prefsFile, JSON.stringify(prefs, null, 2));
1326
+ }
1327
+
1197
1328
  // Find available port
1198
1329
  const net = await import('node:net');
1199
1330
  const port = await new Promise<number>((resolve, reject) => {
@@ -1205,7 +1336,7 @@ export function createAgentRoutes(opts: {
1205
1336
  srv.on('error', reject);
1206
1337
  });
1207
1338
 
1208
- // Launch Chrome with meeting-optimized flags
1339
+ // Launch Chrome with meeting-optimized flags and realistic profile
1209
1340
  const chromeArgs = [
1210
1341
  `--remote-debugging-port=${port}`,
1211
1342
  '--remote-debugging-address=127.0.0.1',
@@ -1215,23 +1346,35 @@ export function createAgentRoutes(opts: {
1215
1346
  '--disable-sync',
1216
1347
  '--disable-translate',
1217
1348
  '--metrics-recording-only',
1218
- '--no-sandbox',
1219
1349
  // Meeting-specific: auto-grant camera/mic permissions
1220
1350
  '--use-fake-ui-for-media-stream',
1221
1351
  '--auto-accept-camera-and-microphone-capture',
1222
- // Use virtual audio
1223
- '--use-fake-device-for-media-stream',
1352
+ // Anti-detection: remove automation indicators
1353
+ '--disable-blink-features=AutomationControlled',
1354
+ '--disable-infobars',
1224
1355
  // Window size for meeting UI
1225
1356
  '--window-size=1920,1080',
1226
1357
  '--start-maximized',
1227
- // User data dir for persistent logins
1228
- `/tmp/meeting-browser-${agentId.slice(0, 8)}`,
1358
+ // Use the agent's persistent profile directory
1359
+ `--user-data-dir=${profileDir}`,
1360
+ // Realistic user-agent lang
1361
+ '--lang=en-US',
1229
1362
  ];
1230
1363
 
1364
+ // Add --no-sandbox on Linux (required for non-root in containers)
1365
+ if (process.platform === 'linux') {
1366
+ chromeArgs.push('--no-sandbox');
1367
+ }
1368
+
1369
+ // Detect display environment
1370
+ const display = process.env.DISPLAY || (process.platform === 'linux' ? ':99' : undefined);
1371
+ const envVars: Record<string, string> = { ...process.env } as any;
1372
+ if (display) envVars.DISPLAY = display;
1373
+
1231
1374
  const child = spawn(chromePath, chromeArgs, {
1232
1375
  detached: true,
1233
1376
  stdio: 'ignore',
1234
- env: { ...process.env, DISPLAY: ':99' },
1377
+ env: envVars,
1235
1378
  });
1236
1379
  child.unref();
1237
1380
 
@@ -1328,15 +1471,45 @@ export function createAgentRoutes(opts: {
1328
1471
 
1329
1472
  try {
1330
1473
  if (provider === 'local') {
1331
- // Test local Chromium availability
1474
+ // Test local Chromium availability — auto-detect across platforms
1332
1475
  try {
1333
1476
  const { execSync } = await import('node:child_process');
1334
- const chromePath = cfg.executablePath || process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || '/usr/bin/chromium';
1335
- const version = execSync(`${chromePath} --version 2>/dev/null || echo "not found"`, { timeout: 5000 }).toString().trim();
1477
+ const { existsSync } = await import('node:fs');
1478
+ const { homedir } = await import('node:os');
1479
+ const { join } = await import('node:path');
1480
+ const candidates = [
1481
+ cfg.executablePath,
1482
+ process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
1483
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
1484
+ '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable',
1485
+ '/usr/bin/chromium', '/usr/bin/chromium-browser', '/snap/bin/chromium',
1486
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
1487
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
1488
+ ].filter(Boolean) as string[];
1489
+ let foundPath = '';
1490
+ for (const p of candidates) { if (existsSync(p)) { foundPath = p; break; } }
1491
+ // Also check Playwright bundled chromium
1492
+ if (!foundPath) {
1493
+ const pwCache = join(homedir(), '.cache', 'ms-playwright');
1494
+ if (existsSync(pwCache)) {
1495
+ const { readdirSync } = await import('node:fs');
1496
+ const dirs = readdirSync(pwCache).filter((d: string) => d.startsWith('chromium-')).sort().reverse();
1497
+ for (const d of dirs) {
1498
+ const linuxP = join(pwCache, d, 'chrome-linux', 'chrome');
1499
+ const macP = join(pwCache, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
1500
+ if (existsSync(linuxP)) { foundPath = linuxP; break; }
1501
+ if (existsSync(macP)) { foundPath = macP; break; }
1502
+ }
1503
+ }
1504
+ }
1505
+ if (!foundPath) {
1506
+ return c.json({ error: 'No Chrome or Chromium found. Install Google Chrome or run: npx playwright install chromium' });
1507
+ }
1508
+ const version = execSync(`"${foundPath}" --version 2>/dev/null || echo "not found"`, { timeout: 5000 }).toString().trim();
1336
1509
  if (version.includes('not found')) {
1337
- return c.json({ error: 'Chromium not found at ' + chromePath });
1510
+ return c.json({ error: 'Chrome found at ' + foundPath + ' but --version failed' });
1338
1511
  }
1339
- return c.json({ ok: true, browserVersion: version, provider: 'local' });
1512
+ return c.json({ ok: true, browserVersion: version, provider: 'local', path: foundPath });
1340
1513
  } catch (e: any) {
1341
1514
  return c.json({ error: 'Chromium not available: ' + e.message });
1342
1515
  }
@@ -1405,6 +1578,20 @@ export function createAgentRoutes(opts: {
1405
1578
  }
1406
1579
  }
1407
1580
 
1581
+ if (provider === 'scrapingbee') {
1582
+ if (!cfg.scrapingbeeApiKey) return c.json({ error: 'ScrapingBee API key not configured' });
1583
+ try {
1584
+ const resp = await fetch(`https://app.scrapingbee.com/api/v1/usage?api_key=${cfg.scrapingbeeApiKey}`, {
1585
+ signal: AbortSignal.timeout(10000),
1586
+ });
1587
+ if (!resp.ok) return c.json({ error: `ScrapingBee returned ${resp.status}` });
1588
+ const data = await resp.json() as any;
1589
+ return c.json({ ok: true, provider: 'scrapingbee', creditsUsed: data.used_api_credit, creditsMax: data.max_api_credit });
1590
+ } catch (e: any) {
1591
+ return c.json({ error: 'Cannot connect to ScrapingBee: ' + e.message });
1592
+ }
1593
+ }
1594
+
1408
1595
  return c.json({ ok: true, provider, note: 'Connection test not implemented for this provider' });
1409
1596
  } catch (e: any) {
1410
1597
  return c.json({ error: e.message });
package/src/server.ts CHANGED
@@ -486,6 +486,18 @@ export function createServer(config: ServerConfig): ServerInstance {
486
486
  app.get('/', (c) => c.redirect('/dashboard'));
487
487
  app.get('/dashboard', serveDashboard);
488
488
 
489
+ // Serve documentation pages (browser-providers, etc.)
490
+ app.get('/docs/:page', (c) => {
491
+ const page = c.req.param('page').replace(/[^a-z0-9-]/gi, ''); // sanitize
492
+ const dir = dirname(fileURLToPath(import.meta.url));
493
+ const filePath = join(dir, 'dashboard', 'docs', page + '.html');
494
+ if (existsSync(filePath)) {
495
+ const content = readFileSync(filePath, 'utf-8');
496
+ return new Response(content, { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
497
+ }
498
+ return c.json({ error: 'Documentation page not found' }, 404);
499
+ });
500
+
489
501
  // Serve dashboard JS modules and static assets (components/*.js, pages/*.js, app.js, assets/*)
490
502
  const STATIC_MIME: Record<string, string> = { '.js': 'application/javascript; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.gif': 'image/gif', '.webp': 'image/webp', '.css': 'text/css; charset=utf-8' };
491
503
  app.get('/dashboard/*', (c) => {