@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.
- package/dist/agent-heartbeat-FF6ASUZ4.js +510 -0
- package/dist/agent-heartbeat-QTYUXYH3.js +510 -0
- package/dist/agent-tools-X4H3KDRM.js +13855 -0
- package/dist/chunk-D5CKFCEL.js +2594 -0
- package/dist/chunk-DD36VGKL.js +3778 -0
- package/dist/chunk-FORLAPWB.js +4463 -0
- package/dist/chunk-J3J5XEWS.js +4463 -0
- package/dist/chunk-MCQ2Q4SC.js +3778 -0
- package/dist/chunk-T4DN77PH.js +1224 -0
- package/dist/chunk-TCQTHG7L.js +1224 -0
- package/dist/cli-agent-G4FLS4M5.js +1721 -0
- package/dist/cli-agent-HDEPHBZX.js +1721 -0
- package/dist/cli-serve-CO5UAKD2.js +114 -0
- package/dist/cli-serve-OHDYAC3L.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/docs/browser-providers.html +302 -0
- package/dist/dashboard/pages/agent-detail/meeting-browser.js +36 -23
- package/dist/index.js +3 -3
- package/dist/meetings-RR72OBSV.js +12 -0
- package/dist/routes-4YJNJJSS.js +13245 -0
- package/dist/routes-QU3MYXUG.js +13063 -0
- package/dist/runtime-2AQSHJKC.js +45 -0
- package/dist/runtime-A7DA7F74.js +45 -0
- package/dist/server-IQME5P6B.js +15 -0
- package/dist/server-VXVRSEFL.js +15 -0
- package/dist/setup-AEBSFYUE.js +20 -0
- package/dist/setup-OBCJB3R6.js +20 -0
- package/package.json +2 -2
- package/src/agent-tools/tools/browser.ts +375 -1
- package/src/dashboard/docs/browser-providers.html +302 -0
- package/src/dashboard/pages/agent-detail/meeting-browser.js +36 -23
- package/src/engine/agent-routes.ts +200 -13
- package/src/server.ts +12 -0
|
@@ -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.
|
|
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
|
-
|
|
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': {
|