@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.
@@ -0,0 +1,45 @@
1
+ import {
2
+ AgentRuntime,
3
+ EmailChannel,
4
+ FollowUpScheduler,
5
+ SessionManager,
6
+ SubAgentManager,
7
+ ToolRegistry,
8
+ callLLM,
9
+ createAgentRuntime,
10
+ createNoopHooks,
11
+ createRuntimeHooks,
12
+ estimateMessageTokens,
13
+ estimateTokens,
14
+ executeTool,
15
+ runAgentLoop,
16
+ toolsToDefinitions
17
+ } from "./chunk-FORLAPWB.js";
18
+ import {
19
+ PROVIDER_REGISTRY,
20
+ listAllProviders,
21
+ resolveApiKeyForProvider,
22
+ resolveProvider
23
+ } from "./chunk-UF3ZJMJO.js";
24
+ import "./chunk-KFQGP6VL.js";
25
+ export {
26
+ AgentRuntime,
27
+ EmailChannel,
28
+ FollowUpScheduler,
29
+ PROVIDER_REGISTRY,
30
+ SessionManager,
31
+ SubAgentManager,
32
+ ToolRegistry,
33
+ callLLM,
34
+ createAgentRuntime,
35
+ createNoopHooks,
36
+ createRuntimeHooks,
37
+ estimateMessageTokens,
38
+ estimateTokens,
39
+ executeTool,
40
+ listAllProviders,
41
+ resolveApiKeyForProvider,
42
+ resolveProvider,
43
+ runAgentLoop,
44
+ toolsToDefinitions
45
+ };
@@ -0,0 +1,45 @@
1
+ import {
2
+ AgentRuntime,
3
+ EmailChannel,
4
+ FollowUpScheduler,
5
+ SessionManager,
6
+ SubAgentManager,
7
+ ToolRegistry,
8
+ callLLM,
9
+ createAgentRuntime,
10
+ createNoopHooks,
11
+ createRuntimeHooks,
12
+ estimateMessageTokens,
13
+ estimateTokens,
14
+ executeTool,
15
+ runAgentLoop,
16
+ toolsToDefinitions
17
+ } from "./chunk-J3J5XEWS.js";
18
+ import {
19
+ PROVIDER_REGISTRY,
20
+ listAllProviders,
21
+ resolveApiKeyForProvider,
22
+ resolveProvider
23
+ } from "./chunk-UF3ZJMJO.js";
24
+ import "./chunk-KFQGP6VL.js";
25
+ export {
26
+ AgentRuntime,
27
+ EmailChannel,
28
+ FollowUpScheduler,
29
+ PROVIDER_REGISTRY,
30
+ SessionManager,
31
+ SubAgentManager,
32
+ ToolRegistry,
33
+ callLLM,
34
+ createAgentRuntime,
35
+ createNoopHooks,
36
+ createRuntimeHooks,
37
+ estimateMessageTokens,
38
+ estimateTokens,
39
+ executeTool,
40
+ listAllProviders,
41
+ resolveApiKeyForProvider,
42
+ resolveProvider,
43
+ runAgentLoop,
44
+ toolsToDefinitions
45
+ };
@@ -0,0 +1,15 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-DD36VGKL.js";
4
+ import "./chunk-OF4MUWWS.js";
5
+ import "./chunk-UF3ZJMJO.js";
6
+ import "./chunk-3OC6RH7W.js";
7
+ import "./chunk-2DDKGTD6.js";
8
+ import "./chunk-YVK6F5OD.js";
9
+ import "./chunk-MKRNEM5A.js";
10
+ import "./chunk-DRXMYYKN.js";
11
+ import "./chunk-6WSX7QXF.js";
12
+ import "./chunk-KFQGP6VL.js";
13
+ export {
14
+ createServer
15
+ };
@@ -0,0 +1,15 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-MCQ2Q4SC.js";
4
+ import "./chunk-OF4MUWWS.js";
5
+ import "./chunk-UF3ZJMJO.js";
6
+ import "./chunk-3OC6RH7W.js";
7
+ import "./chunk-2DDKGTD6.js";
8
+ import "./chunk-YVK6F5OD.js";
9
+ import "./chunk-MKRNEM5A.js";
10
+ import "./chunk-DRXMYYKN.js";
11
+ import "./chunk-6WSX7QXF.js";
12
+ import "./chunk-KFQGP6VL.js";
13
+ export {
14
+ createServer
15
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-TCQTHG7L.js";
10
+ import "./chunk-ULRBF2T7.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-T4DN77PH.js";
10
+ import "./chunk-ULRBF2T7.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.237",
3
+ "version": "0.5.239",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  }
17
17
  },
18
18
  "scripts": {
19
- "build": "tsup src/index.ts src/cli.ts src/registry/cli.ts --format esm --external better-sqlite3 --external mongodb --external mysql2 --external @libsql/client --external @aws-sdk/client-dynamodb --external @aws-sdk/lib-dynamodb --external @aws-sdk/client-s3 --external @aws-sdk/s3-request-presigner --external @google-cloud/storage --external @azure/storage-blob --external @mozilla/readability --external imapflow --external nodemailer --external linkedom --external postgres --external playwright-core --external ws --external express && mkdir -p dist/dashboard/components dist/dashboard/pages dist/dashboard/vendor dist/dashboard/assets dist/registry && cp src/dashboard/index.html dist/dashboard/ && cp src/dashboard/app.js dist/dashboard/ && cp src/dashboard/components/*.js dist/dashboard/components/ && cp src/dashboard/pages/*.js dist/dashboard/pages/ && rm -rf dist/dashboard/pages/agent-detail && cp -r src/dashboard/pages/agent-detail dist/dashboard/pages/agent-detail && cp src/dashboard/vendor/*.js dist/dashboard/vendor/ && cp -r src/dashboard/assets/* dist/dashboard/assets/ && mkdir -p dist/dashboard/data && cp src/dashboard/data/*.js dist/dashboard/data/ && mkdir -p dist/assets && cp src/engine/assets/* dist/assets/ && cp src/engine/soul-templates.json dist/",
19
+ "build": "tsup src/index.ts src/cli.ts src/registry/cli.ts --format esm --external better-sqlite3 --external mongodb --external mysql2 --external @libsql/client --external @aws-sdk/client-dynamodb --external @aws-sdk/lib-dynamodb --external @aws-sdk/client-s3 --external @aws-sdk/s3-request-presigner --external @google-cloud/storage --external @azure/storage-blob --external @mozilla/readability --external imapflow --external nodemailer --external linkedom --external postgres --external playwright-core --external ws --external express && mkdir -p dist/dashboard/components dist/dashboard/pages dist/dashboard/vendor dist/dashboard/assets dist/registry && cp src/dashboard/index.html dist/dashboard/ && cp src/dashboard/app.js dist/dashboard/ && cp src/dashboard/components/*.js dist/dashboard/components/ && cp src/dashboard/pages/*.js dist/dashboard/pages/ && rm -rf dist/dashboard/pages/agent-detail && cp -r src/dashboard/pages/agent-detail dist/dashboard/pages/agent-detail && cp src/dashboard/vendor/*.js dist/dashboard/vendor/ && cp -r src/dashboard/assets/* dist/dashboard/assets/ && mkdir -p dist/dashboard/data && cp src/dashboard/data/*.js dist/dashboard/data/ && mkdir -p dist/dashboard/docs && cp src/dashboard/docs/*.html dist/dashboard/docs/ && mkdir -p dist/assets && cp src/engine/assets/* dist/assets/ && cp src/engine/soul-templates.json dist/",
20
20
  "dev": "npm run build && node --watch start-live.mjs",
21
21
  "rebuild": "npm run build && pm2 restart enterprise"
22
22
  },
@@ -217,6 +217,373 @@ export async function ensureBrowser(headless: boolean, agentId: string, useChrom
217
217
  }
218
218
  }
219
219
 
220
+ // ═══════════════════════════════════════════════════════════
221
+ // Cloud/Remote Browser Provider Integration
222
+ // ═══════════════════════════════════════════════════════════
223
+
224
+ /** Browser config from managed agent settings */
225
+ export interface BrowserProviderConfig {
226
+ provider?: 'local' | 'remote-cdp' | 'browserless' | 'browserbase' | 'steel' | 'scrapingbee';
227
+ enabled?: boolean;
228
+ headless?: boolean;
229
+ // Remote CDP
230
+ cdpUrl?: string;
231
+ cdpAuthToken?: string;
232
+ cdpTimeout?: number;
233
+ sshTunnel?: string;
234
+ // Browserless
235
+ browserlessToken?: string;
236
+ browserlessEndpoint?: string;
237
+ browserlessConcurrency?: number;
238
+ browserlessStealth?: boolean;
239
+ browserlessProxy?: string;
240
+ // Browserbase
241
+ browserbaseApiKey?: string;
242
+ browserbaseProjectId?: string;
243
+ browserbaseRecording?: boolean;
244
+ browserbaseKeepAlive?: boolean;
245
+ // Steel
246
+ steelApiKey?: string;
247
+ steelEndpoint?: string;
248
+ steelSessionDuration?: number;
249
+ // ScrapingBee
250
+ scrapingbeeApiKey?: string;
251
+ scrapingbeeJsRendering?: boolean;
252
+ scrapingbeePremiumProxy?: boolean;
253
+ scrapingbeeCountry?: string;
254
+ // Local overrides
255
+ executablePath?: string;
256
+ userDataDir?: string;
257
+ extraArgs?: string[];
258
+ // Security
259
+ timeoutMs?: number;
260
+ maxPages?: number;
261
+ blockedDomains?: string[];
262
+ allowedDomains?: string[];
263
+ }
264
+
265
+ /** Active SSH tunnel process tracking */
266
+ var activeSshTunnels = new Map<string, any>();
267
+
268
+ /**
269
+ * Establish SSH tunnel if configured. Runs ssh in background and waits
270
+ * for the port to become available. Kills stale tunnels automatically.
271
+ */
272
+ async function ensureSshTunnel(tunnelCmd: string, agentId: string): Promise<void> {
273
+ var tunnelKey = `ssh-tunnel:${agentId}`;
274
+ var existing = activeSshTunnels.get(tunnelKey);
275
+ if (existing && !existing.killed) {
276
+ // Tunnel already running — verify it's alive
277
+ try {
278
+ existing.kill(0); // Signal 0 just checks process existence
279
+ return;
280
+ } catch {
281
+ activeSshTunnels.delete(tunnelKey);
282
+ }
283
+ }
284
+
285
+ console.log(`[browser] Establishing SSH tunnel for ${agentId}: ${tunnelCmd}`);
286
+ const { spawn } = await import('node:child_process');
287
+
288
+ // Parse tunnel command — extract just the SSH args, ignore "ssh" prefix
289
+ var args = tunnelCmd.replace(/^ssh\s+/, '').split(/\s+/).filter(Boolean);
290
+ // Add -N (no remote command) and -f would daemonize but we manage it ourselves
291
+ if (!args.includes('-N')) args.push('-N');
292
+ // Add StrictHostKeyChecking=accept-new for first-time connections
293
+ if (!args.some(a => a.includes('StrictHostKeyChecking'))) {
294
+ args.push('-o', 'StrictHostKeyChecking=accept-new');
295
+ }
296
+ // Add ServerAliveInterval to keep tunnel alive
297
+ if (!args.some(a => a.includes('ServerAliveInterval'))) {
298
+ args.push('-o', 'ServerAliveInterval=30');
299
+ }
300
+
301
+ var proc = spawn('ssh', args, { stdio: 'pipe', detached: false });
302
+ activeSshTunnels.set(tunnelKey, proc);
303
+
304
+ proc.on('exit', () => { activeSshTunnels.delete(tunnelKey); });
305
+ proc.on('error', (err: any) => {
306
+ console.error(`[browser] SSH tunnel error for ${agentId}:`, err.message);
307
+ activeSshTunnels.delete(tunnelKey);
308
+ });
309
+
310
+ // Wait for tunnel to be ready (port becomes connectable)
311
+ // Extract local port from -L flag: -L localPort:host:remotePort
312
+ var localPortMatch = tunnelCmd.match(/-L\s*(\d+):/);
313
+ if (localPortMatch) {
314
+ var port = parseInt(localPortMatch[1]);
315
+ var maxWait = 15000;
316
+ var start = Date.now();
317
+ const net = await import('node:net');
318
+ while (Date.now() - start < maxWait) {
319
+ var connected = await new Promise<boolean>((resolve) => {
320
+ var sock = net.connect({ port, host: '127.0.0.1' }, () => { sock.destroy(); resolve(true); });
321
+ sock.on('error', () => resolve(false));
322
+ sock.setTimeout(1000, () => { sock.destroy(); resolve(false); });
323
+ });
324
+ if (connected) {
325
+ console.log(`[browser] SSH tunnel ready on port ${port} for ${agentId}`);
326
+ return;
327
+ }
328
+ await new Promise(r => setTimeout(r, 500));
329
+ }
330
+ console.warn(`[browser] SSH tunnel port ${port} not ready after ${maxWait}ms — proceeding anyway`);
331
+ } else {
332
+ // No -L flag found, just wait a bit for the tunnel to establish
333
+ await new Promise(r => setTimeout(r, 3000));
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Connect to a remote browser via Chrome DevTools Protocol.
339
+ * Supports direct WebSocket connections and SSH tunneling.
340
+ */
341
+ async function connectRemoteCDP(cfg: BrowserProviderConfig, agentId: string): Promise<{ page: any }> {
342
+ var contextKey = `${agentId}:remote-cdp`;
343
+ var existing = agentContexts.get(contextKey);
344
+ if (existing) { existing.lastUsed = Date.now(); return { page: existing.page }; }
345
+
346
+ // Establish SSH tunnel if configured
347
+ if (cfg.sshTunnel) {
348
+ await ensureSshTunnel(cfg.sshTunnel, agentId);
349
+ }
350
+
351
+ var pw = await import('playwright');
352
+ var cdpUrl = cfg.cdpUrl;
353
+ if (!cdpUrl) throw new Error('CDP WebSocket URL not configured. Go to Settings → Browser → Remote CDP and enter the WebSocket URL.');
354
+
355
+ // If user provided a bare host:port, auto-discover the WebSocket URL
356
+ if (!cdpUrl.startsWith('ws://') && !cdpUrl.startsWith('wss://')) {
357
+ // Treat as http endpoint — query /json/version for the real WS URL
358
+ var httpUrl = cdpUrl.startsWith('http') ? cdpUrl : `http://${cdpUrl}`;
359
+ if (!httpUrl.includes('/json/version')) httpUrl = httpUrl.replace(/\/$/, '') + '/json/version';
360
+ try {
361
+ var resp = await fetch(httpUrl, {
362
+ signal: AbortSignal.timeout(cfg.cdpTimeout || 10000),
363
+ headers: cfg.cdpAuthToken ? { 'Authorization': `Bearer ${cfg.cdpAuthToken}` } : {},
364
+ });
365
+ var versionData = await resp.json() as any;
366
+ cdpUrl = versionData.webSocketDebuggerUrl;
367
+ if (!cdpUrl) throw new Error('No webSocketDebuggerUrl in /json/version response');
368
+ console.log(`[browser] Auto-discovered CDP WebSocket: ${cdpUrl}`);
369
+ } catch (e: any) {
370
+ throw new Error(`Cannot discover CDP endpoint from ${httpUrl}: ${e.message}. Provide a full ws:// URL instead.`);
371
+ }
372
+ }
373
+
374
+ var timeout = cfg.cdpTimeout || 30000;
375
+ var headers: Record<string, string> = {};
376
+ if (cfg.cdpAuthToken) headers['Authorization'] = `Bearer ${cfg.cdpAuthToken}`;
377
+
378
+ console.log(`[browser] Connecting to remote CDP: ${cdpUrl} (agent: ${agentId})`);
379
+ var browser = await pw.chromium.connectOverCDP(cdpUrl, { timeout, headers });
380
+ var contexts = browser.contexts();
381
+ var context = contexts[0] || await browser.newContext({
382
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT },
383
+ });
384
+ var page = context.pages()[0] || await context.newPage();
385
+
386
+ agentContexts.set(contextKey, { context: browser, page, lastUsed: Date.now() });
387
+ startIdleCleanup();
388
+ console.log(`[browser] Connected to remote CDP for ${agentId}`);
389
+ return { page };
390
+ }
391
+
392
+ /**
393
+ * Connect to Browserless.io cloud browser service.
394
+ * Creates a new session via WebSocket using the API token.
395
+ */
396
+ async function connectBrowserless(cfg: BrowserProviderConfig, agentId: string): Promise<{ page: any }> {
397
+ var contextKey = `${agentId}:browserless`;
398
+ var existing = agentContexts.get(contextKey);
399
+ if (existing) { existing.lastUsed = Date.now(); return { page: existing.page }; }
400
+
401
+ if (!cfg.browserlessToken) throw new Error('Browserless API token not configured. Go to Settings → Browser and enter your token.');
402
+
403
+ var pw = await import('playwright');
404
+ var endpoint = cfg.browserlessEndpoint || 'wss://chrome.browserless.io';
405
+ // Normalize endpoint — ensure it's a WebSocket URL
406
+ if (endpoint.startsWith('http://')) endpoint = endpoint.replace('http://', 'ws://');
407
+ if (endpoint.startsWith('https://')) endpoint = endpoint.replace('https://', 'wss://');
408
+ if (!endpoint.startsWith('ws://') && !endpoint.startsWith('wss://')) endpoint = 'wss://' + endpoint;
409
+
410
+ // Build connection URL with token and options
411
+ var connectUrl = `${endpoint}?token=${encodeURIComponent(cfg.browserlessToken)}`;
412
+ if (cfg.browserlessStealth) connectUrl += '&stealth';
413
+ if (cfg.browserlessProxy) connectUrl += `&--proxy-server=${encodeURIComponent(cfg.browserlessProxy)}`;
414
+
415
+ console.log(`[browser] Connecting to Browserless for ${agentId}`);
416
+ var browser = await pw.chromium.connectOverCDP(connectUrl, { timeout: 30000 });
417
+ var context = await browser.newContext({
418
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT },
419
+ });
420
+ var page = await context.newPage();
421
+
422
+ agentContexts.set(contextKey, { context: browser, page, lastUsed: Date.now() });
423
+ startIdleCleanup();
424
+ console.log(`[browser] Connected to Browserless for ${agentId}`);
425
+ return { page };
426
+ }
427
+
428
+ /**
429
+ * Connect to Browserbase cloud browser.
430
+ * Creates a session via REST API, then connects via CDP WebSocket.
431
+ */
432
+ async function connectBrowserbase(cfg: BrowserProviderConfig, agentId: string): Promise<{ page: any }> {
433
+ var contextKey = `${agentId}:browserbase`;
434
+ var existing = agentContexts.get(contextKey);
435
+ if (existing) { existing.lastUsed = Date.now(); return { page: existing.page }; }
436
+
437
+ if (!cfg.browserbaseApiKey) throw new Error('Browserbase API key not configured. Go to Settings → Browser and enter your API key.');
438
+ if (!cfg.browserbaseProjectId) throw new Error('Browserbase Project ID not configured. Go to Settings → Browser and enter your project ID.');
439
+
440
+ // Step 1: Create a session via REST
441
+ console.log(`[browser] Creating Browserbase session for ${agentId}`);
442
+ var sessionResp = await fetch('https://www.browserbase.com/v1/sessions', {
443
+ method: 'POST',
444
+ headers: { 'x-bb-api-key': cfg.browserbaseApiKey, 'Content-Type': 'application/json' },
445
+ body: JSON.stringify({
446
+ projectId: cfg.browserbaseProjectId,
447
+ browserSettings: {
448
+ recordSession: cfg.browserbaseRecording !== false,
449
+ },
450
+ keepAlive: cfg.browserbaseKeepAlive || false,
451
+ }),
452
+ signal: AbortSignal.timeout(15000),
453
+ });
454
+ if (!sessionResp.ok) {
455
+ var errBody = await sessionResp.text().catch(() => '');
456
+ throw new Error(`Browserbase session creation failed (${sessionResp.status}): ${errBody}`);
457
+ }
458
+ var sessionData = await sessionResp.json() as any;
459
+ var connectUrl = sessionData.connectUrl || `wss://connect.browserbase.com?apiKey=${cfg.browserbaseApiKey}&sessionId=${sessionData.id}`;
460
+
461
+ // Step 2: Connect via CDP
462
+ var pw = await import('playwright');
463
+ var browser = await pw.chromium.connectOverCDP(connectUrl, { timeout: 30000 });
464
+ var context = browser.contexts()[0] || await browser.newContext({
465
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT },
466
+ });
467
+ var page = context.pages()[0] || await context.newPage();
468
+
469
+ agentContexts.set(contextKey, { context: browser, page, lastUsed: Date.now() });
470
+ startIdleCleanup();
471
+ console.log(`[browser] Connected to Browserbase session ${sessionData.id} for ${agentId}`);
472
+ return { page };
473
+ }
474
+
475
+ /**
476
+ * Connect to Steel.dev browser API.
477
+ * Creates a session, then connects via CDP WebSocket.
478
+ */
479
+ async function connectSteel(cfg: BrowserProviderConfig, agentId: string): Promise<{ page: any }> {
480
+ var contextKey = `${agentId}:steel`;
481
+ var existing = agentContexts.get(contextKey);
482
+ if (existing) { existing.lastUsed = Date.now(); return { page: existing.page }; }
483
+
484
+ if (!cfg.steelApiKey) throw new Error('Steel API key not configured. Go to Settings → Browser and enter your API key.');
485
+
486
+ var endpoint = cfg.steelEndpoint || 'https://api.steel.dev';
487
+ var duration = (cfg.steelSessionDuration || 15) * 60; // convert min to seconds
488
+
489
+ // Step 1: Create session
490
+ console.log(`[browser] Creating Steel session for ${agentId}`);
491
+ var sessionResp = await fetch(`${endpoint}/v1/sessions`, {
492
+ method: 'POST',
493
+ headers: { 'Authorization': `Bearer ${cfg.steelApiKey}`, 'Content-Type': 'application/json' },
494
+ body: JSON.stringify({ timeout: duration, useProxy: false }),
495
+ signal: AbortSignal.timeout(15000),
496
+ });
497
+ if (!sessionResp.ok) {
498
+ var errBody = await sessionResp.text().catch(() => '');
499
+ throw new Error(`Steel session creation failed (${sessionResp.status}): ${errBody}`);
500
+ }
501
+ var sessionData = await sessionResp.json() as any;
502
+ var connectUrl = sessionData.connectUrl || sessionData.websocketUrl || sessionData.cdpUrl;
503
+ if (!connectUrl) throw new Error('Steel session created but no connection URL returned');
504
+
505
+ // Step 2: Connect via CDP
506
+ var pw = await import('playwright');
507
+ var browser = await pw.chromium.connectOverCDP(connectUrl, { timeout: 30000 });
508
+ var context = browser.contexts()[0] || await browser.newContext({
509
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT },
510
+ });
511
+ var page = context.pages()[0] || await context.newPage();
512
+
513
+ agentContexts.set(contextKey, { context: browser, page, lastUsed: Date.now() });
514
+ startIdleCleanup();
515
+ console.log(`[browser] Connected to Steel session ${sessionData.id} for ${agentId}`);
516
+ return { page };
517
+ }
518
+
519
+ /**
520
+ * ScrapingBee proxy-based browser. Uses ScrapingBee's rendering API
521
+ * as a fetch proxy rather than a CDP connection (ScrapingBee doesn't expose CDP).
522
+ * For browser tool compatibility, we launch a local Chromium and route
523
+ * requests through ScrapingBee's proxy endpoint.
524
+ */
525
+ async function connectScrapingBee(cfg: BrowserProviderConfig, agentId: string): Promise<{ page: any }> {
526
+ var contextKey = `${agentId}:scrapingbee`;
527
+ var existing = agentContexts.get(contextKey);
528
+ if (existing) { existing.lastUsed = Date.now(); return { page: existing.page }; }
529
+
530
+ if (!cfg.scrapingbeeApiKey) throw new Error('ScrapingBee API key not configured. Go to Settings → Browser and enter your API key.');
531
+
532
+ // ScrapingBee uses a proxy approach: we route the local browser through their proxy
533
+ // This gives us full Playwright control while ScrapingBee handles anti-detection + proxies
534
+ var pw = await import('playwright');
535
+ var proxyUrl = `http://${cfg.scrapingbeeApiKey}:${cfg.scrapingbeeJsRendering !== false ? 'render_js' : 'norender'}${cfg.scrapingbeePremiumProxy ? '&premium_proxy=true' : ''}${cfg.scrapingbeeCountry ? `&country_code=${cfg.scrapingbeeCountry}` : ''}@proxy.scrapingbee.com:8886`;
536
+
537
+ console.log(`[browser] Launching browser with ScrapingBee proxy for ${agentId}`);
538
+ var browser = await pw.chromium.launch({
539
+ headless: true,
540
+ proxy: { server: proxyUrl },
541
+ });
542
+ var context = await browser.newContext({
543
+ viewport: { width: DEFAULT_VIEWPORT_WIDTH, height: DEFAULT_VIEWPORT_HEIGHT },
544
+ ignoreHTTPSErrors: true, // ScrapingBee proxy uses self-signed certs
545
+ });
546
+ var page = await context.newPage();
547
+
548
+ agentContexts.set(contextKey, { context: browser, page, lastUsed: Date.now() });
549
+ startIdleCleanup();
550
+ console.log(`[browser] ScrapingBee proxy browser ready for ${agentId}`);
551
+ return { page };
552
+ }
553
+
554
+ /**
555
+ * Unified browser provider router. Reads config and dispatches to the correct
556
+ * connection method. Falls back to local Chromium if provider is unset or 'local'.
557
+ */
558
+ export async function ensureBrowserFromConfig(
559
+ cfg: BrowserProviderConfig | undefined,
560
+ agentId: string,
561
+ modeOverride?: BrowserMode
562
+ ): Promise<{ page: any }> {
563
+ var provider = cfg?.provider || 'local';
564
+
565
+ // Mode override (e.g., agent explicitly requested chrome mode) takes precedence
566
+ if (modeOverride === 'chrome' || modeOverride === 'headed') {
567
+ return ensureBrowser(false, agentId, modeOverride === 'chrome');
568
+ }
569
+
570
+ switch (provider) {
571
+ case 'remote-cdp':
572
+ return connectRemoteCDP(cfg!, agentId);
573
+ case 'browserless':
574
+ return connectBrowserless(cfg!, agentId);
575
+ case 'browserbase':
576
+ return connectBrowserbase(cfg!, agentId);
577
+ case 'steel':
578
+ return connectSteel(cfg!, agentId);
579
+ case 'scrapingbee':
580
+ return connectScrapingBee(cfg!, agentId);
581
+ case 'local':
582
+ default:
583
+ return ensureBrowser(cfg?.headless !== false, agentId, false);
584
+ }
585
+ }
586
+
220
587
  export function createBrowserTool(options?: ToolCreationOptions & {
221
588
  ssrfGuard?: SsrfGuard;
222
589
  }): AnyAgentTool | null {
@@ -228,6 +595,8 @@ export function createBrowserTool(options?: ToolCreationOptions & {
228
595
  var agentId = options?.agentId || 'default';
229
596
  var sandboxed = options?.sandboxed ?? false;
230
597
  var ssrfGuard = options?.ssrfGuard;
598
+ // Full browser provider config (from managed agent settings)
599
+ var providerConfig = browserConfig as BrowserProviderConfig | undefined;
231
600
 
232
601
  return {
233
602
  name: 'browser',
@@ -286,7 +655,12 @@ export function createBrowserTool(options?: ToolCreationOptions & {
286
655
  }
287
656
  }
288
657
 
289
- var { page } = await ensureBrowser(useHeadless, agentId, useChrome);
658
+ // Use provider-aware connection if a cloud/remote provider is configured
659
+ var modeOverride: BrowserMode | undefined = useChrome ? 'chrome' : (!useHeadless ? 'headed' : undefined);
660
+ var hasCloudProvider = providerConfig?.provider && providerConfig.provider !== 'local';
661
+ var { page } = hasCloudProvider && !modeOverride
662
+ ? await ensureBrowserFromConfig(providerConfig, agentId)
663
+ : await ensureBrowser(useHeadless, agentId, useChrome);
290
664
 
291
665
  switch (action) {
292
666
  case 'navigate': {