@agenticmail/enterprise 0.5.240 → 0.5.242
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-HVYFITSR.js +510 -0
- package/dist/agent-heartbeat-IZSQY2W4.js +510 -0
- package/dist/chunk-3VULBHMF.js +1224 -0
- package/dist/chunk-H3NKARYD.js +1224 -0
- package/dist/chunk-PAENAIFM.js +4463 -0
- package/dist/chunk-POBOGETZ.js +4463 -0
- package/dist/chunk-SQ42SAUY.js +3778 -0
- package/dist/chunk-YJ2OELZW.js +3778 -0
- package/dist/cli-agent-4GQDZBCP.js +1721 -0
- package/dist/cli-agent-WUIV3COT.js +1721 -0
- package/dist/cli-serve-GUJAWQUB.js +114 -0
- package/dist/cli-serve-HOEKOOUY.js +114 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/app.js +1 -1
- package/dist/dashboard/pages/skill-connections.js +673 -454
- package/dist/index.js +3 -3
- package/dist/routes-GLJUKYTG.js +13271 -0
- package/dist/routes-Z4PGYK4X.js +13487 -0
- package/dist/runtime-MEOBOFBE.js +45 -0
- package/dist/runtime-YK2UHIXP.js +45 -0
- package/dist/server-LBPJUKWA.js +15 -0
- package/dist/server-UV2MP24J.js +15 -0
- package/dist/setup-3NI7QF2S.js +20 -0
- package/dist/setup-LLZ6NTTG.js +20 -0
- package/package.json +1 -1
- package/src/dashboard/app.js +1 -1
- package/src/dashboard/pages/skill-connections.js +673 -454
- package/src/engine/agent-routes.ts +27 -9
- package/src/engine/routes.ts +213 -0
|
@@ -1154,6 +1154,9 @@ export function createAgentRoutes(opts: {
|
|
|
1154
1154
|
return c.json({ config: managed.config?.browserConfig || {} });
|
|
1155
1155
|
});
|
|
1156
1156
|
|
|
1157
|
+
// ── In-memory registry of running meeting browsers (survives config save/reload issues) ──
|
|
1158
|
+
const meetingBrowsers = new Map<string, { port: number; cdpUrl: string; pid?: number }>();
|
|
1159
|
+
|
|
1157
1160
|
/**
|
|
1158
1161
|
* POST /bridge/agents/:id/browser-config/launch-meeting-browser
|
|
1159
1162
|
* Launches a meeting-ready headed Chrome instance with virtual display + audio.
|
|
@@ -1267,16 +1270,21 @@ export function createAgentRoutes(opts: {
|
|
|
1267
1270
|
}, 400);
|
|
1268
1271
|
}
|
|
1269
1272
|
|
|
1270
|
-
// Check if a meeting browser is already running for this agent
|
|
1271
|
-
const
|
|
1273
|
+
// Check if a meeting browser is already running for this agent (in-memory registry first, then config fallback)
|
|
1274
|
+
const tracked = meetingBrowsers.get(agentId);
|
|
1275
|
+
const existingPort = tracked?.port || (managed.config as any)?.meetingBrowserPort;
|
|
1272
1276
|
if (existingPort) {
|
|
1273
1277
|
try {
|
|
1274
1278
|
const resp = await fetch(`http://127.0.0.1:${existingPort}/json/version`, { signal: AbortSignal.timeout(2000) });
|
|
1275
1279
|
if (resp.ok) {
|
|
1276
1280
|
const data = await resp.json() as any;
|
|
1281
|
+
// Ensure registry is up to date
|
|
1282
|
+
meetingBrowsers.set(agentId, { port: existingPort, cdpUrl: data.webSocketDebuggerUrl, pid: tracked?.pid });
|
|
1277
1283
|
return c.json({ ok: true, alreadyRunning: true, cdpUrl: data.webSocketDebuggerUrl, port: existingPort, browserVersion: data.Browser });
|
|
1278
1284
|
}
|
|
1279
1285
|
} catch { /* not running, will launch new one */ }
|
|
1286
|
+
// Was tracked but not responding — clean up
|
|
1287
|
+
meetingBrowsers.delete(agentId);
|
|
1280
1288
|
}
|
|
1281
1289
|
|
|
1282
1290
|
// ── Create a realistic browser profile using agent identity ──
|
|
@@ -1398,12 +1406,13 @@ export function createAgentRoutes(opts: {
|
|
|
1398
1406
|
return c.json({ error: 'Chrome launched but CDP not responding after 15s' });
|
|
1399
1407
|
}
|
|
1400
1408
|
|
|
1401
|
-
// Save
|
|
1409
|
+
// Save to in-memory registry (primary) and agent config (backup)
|
|
1410
|
+
meetingBrowsers.set(agentId, { port, cdpUrl, pid: child.pid });
|
|
1402
1411
|
if (!managed.config) managed.config = {} as any;
|
|
1403
1412
|
(managed.config as any).meetingBrowserPort = port;
|
|
1404
1413
|
(managed.config as any).meetingBrowserCdpUrl = cdpUrl;
|
|
1405
1414
|
managed.updatedAt = new Date().toISOString();
|
|
1406
|
-
await lifecycle.saveAgent(agentId);
|
|
1415
|
+
try { await lifecycle.saveAgent(agentId); } catch (e) { console.warn('[meeting-browser] Config save failed (non-fatal):', e); }
|
|
1407
1416
|
|
|
1408
1417
|
return c.json({ ok: true, cdpUrl, port, browserVersion, pid: child.pid });
|
|
1409
1418
|
} catch (e: any) {
|
|
@@ -1421,7 +1430,8 @@ export function createAgentRoutes(opts: {
|
|
|
1421
1430
|
if (!managed) return c.json({ error: 'Agent not found' }, 404);
|
|
1422
1431
|
|
|
1423
1432
|
try {
|
|
1424
|
-
const
|
|
1433
|
+
const tracked = meetingBrowsers.get(agentId);
|
|
1434
|
+
const port = tracked?.port || (managed.config as any)?.meetingBrowserPort;
|
|
1425
1435
|
if (!port) return c.json({ error: 'No meeting browser is tracked for this agent' }, 400);
|
|
1426
1436
|
|
|
1427
1437
|
// Try to close gracefully via CDP
|
|
@@ -1440,20 +1450,28 @@ export function createAgentRoutes(opts: {
|
|
|
1440
1450
|
}
|
|
1441
1451
|
} catch { /* not running or not reachable */ }
|
|
1442
1452
|
|
|
1443
|
-
// Fallback: kill by port
|
|
1453
|
+
// Fallback: kill by PID first (most reliable), then by port
|
|
1454
|
+
if (!closed && tracked?.pid) {
|
|
1455
|
+
try { process.kill(tracked.pid, 'SIGTERM'); closed = true; } catch { /* already dead */ }
|
|
1456
|
+
}
|
|
1444
1457
|
if (!closed) {
|
|
1445
1458
|
try {
|
|
1446
1459
|
const { execSync } = await import('node:child_process');
|
|
1447
|
-
|
|
1460
|
+
if (process.platform === 'win32') {
|
|
1461
|
+
execSync(`for /f "tokens=5" %a in ('netstat -ano ^| findstr :${port}') do taskkill /PID %a /F 2>nul`, { timeout: 5000 });
|
|
1462
|
+
} else {
|
|
1463
|
+
execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null || fuser -k ${port}/tcp 2>/dev/null || true`, { timeout: 5000 });
|
|
1464
|
+
}
|
|
1448
1465
|
closed = true;
|
|
1449
1466
|
} catch { /* already dead */ }
|
|
1450
1467
|
}
|
|
1451
1468
|
|
|
1452
|
-
// Clear config
|
|
1469
|
+
// Clear from registry and config
|
|
1470
|
+
meetingBrowsers.delete(agentId);
|
|
1453
1471
|
delete (managed.config as any).meetingBrowserPort;
|
|
1454
1472
|
delete (managed.config as any).meetingBrowserCdpUrl;
|
|
1455
1473
|
managed.updatedAt = new Date().toISOString();
|
|
1456
|
-
await lifecycle.saveAgent(agentId);
|
|
1474
|
+
try { await lifecycle.saveAgent(agentId); } catch (e) { console.warn('[meeting-browser] Config save failed (non-fatal):', e); }
|
|
1457
1475
|
|
|
1458
1476
|
return c.json({ ok: true, stopped: true, port });
|
|
1459
1477
|
} catch (e: any) {
|
package/src/engine/routes.ts
CHANGED
|
@@ -341,6 +341,219 @@ engine.get('/hierarchy/escalations/:agentId', async (c) => {
|
|
|
341
341
|
}
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
// ─── MCP Server Management ──────────────────────────────────────────
|
|
345
|
+
// CRUD for external MCP server connections (stdio, SSE, HTTP)
|
|
346
|
+
engine.get('/mcp-servers', async (c) => {
|
|
347
|
+
try {
|
|
348
|
+
const rows = await engineDb.query(`SELECT * FROM mcp_servers WHERE org_id = $1 ORDER BY created_at DESC`, ['default']);
|
|
349
|
+
const servers = (rows || []).map((r: any) => {
|
|
350
|
+
const config = typeof r.config === 'string' ? JSON.parse(r.config) : (r.config || {});
|
|
351
|
+
return { id: r.id, ...config, status: r.status || 'unknown', toolCount: r.tool_count || 0, tools: r.tools ? (typeof r.tools === 'string' ? JSON.parse(r.tools) : r.tools) : [] };
|
|
352
|
+
});
|
|
353
|
+
return c.json({ servers });
|
|
354
|
+
} catch (e: any) {
|
|
355
|
+
// Table may not exist yet
|
|
356
|
+
if (e.message?.includes('does not exist') || e.message?.includes('no such table')) {
|
|
357
|
+
return c.json({ servers: [] });
|
|
358
|
+
}
|
|
359
|
+
return c.json({ error: e.message }, 500);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
engine.post('/mcp-servers', async (c) => {
|
|
364
|
+
try {
|
|
365
|
+
await engineDb.exec(`CREATE TABLE IF NOT EXISTS mcp_servers (
|
|
366
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
367
|
+
org_id TEXT NOT NULL DEFAULT 'default',
|
|
368
|
+
config JSONB NOT NULL DEFAULT '{}',
|
|
369
|
+
status TEXT DEFAULT 'unknown',
|
|
370
|
+
tool_count INTEGER DEFAULT 0,
|
|
371
|
+
tools JSONB DEFAULT '[]',
|
|
372
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
373
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
374
|
+
)`);
|
|
375
|
+
const body = await c.req.json();
|
|
376
|
+
const id = crypto.randomUUID();
|
|
377
|
+
await engineDb.exec(`INSERT INTO mcp_servers (id, org_id, config) VALUES ($1, $2, $3)`, [id, 'default', JSON.stringify(body)]);
|
|
378
|
+
return c.json({ ok: true, id });
|
|
379
|
+
} catch (e: any) { return c.json({ error: e.message }, 500); }
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
engine.put('/mcp-servers/:id', async (c) => {
|
|
383
|
+
try {
|
|
384
|
+
const id = c.req.param('id');
|
|
385
|
+
const body = await c.req.json();
|
|
386
|
+
// Merge with existing config
|
|
387
|
+
const rows = await engineDb.query(`SELECT config FROM mcp_servers WHERE id = $1`, [id]);
|
|
388
|
+
if (!rows?.length) return c.json({ error: 'Server not found' }, 404);
|
|
389
|
+
const existing = typeof rows[0].config === 'string' ? JSON.parse(rows[0].config) : (rows[0].config || {});
|
|
390
|
+
const merged = { ...existing, ...body };
|
|
391
|
+
await engineDb.exec(`UPDATE mcp_servers SET config = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(merged), id]);
|
|
392
|
+
return c.json({ ok: true });
|
|
393
|
+
} catch (e: any) { return c.json({ error: e.message }, 500); }
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
engine.delete('/mcp-servers/:id', async (c) => {
|
|
397
|
+
try {
|
|
398
|
+
await engineDb.exec(`DELETE FROM mcp_servers WHERE id = $1`, [c.req.param('id')]);
|
|
399
|
+
return c.json({ ok: true });
|
|
400
|
+
} catch (e: any) { return c.json({ error: e.message }, 500); }
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
engine.post('/mcp-servers/:id/test', async (c) => {
|
|
404
|
+
try {
|
|
405
|
+
const id = c.req.param('id');
|
|
406
|
+
const rows = await engineDb.query(`SELECT config FROM mcp_servers WHERE id = $1`, [id]);
|
|
407
|
+
if (!rows?.length) return c.json({ error: 'Server not found' }, 404);
|
|
408
|
+
const config = typeof rows[0].config === 'string' ? JSON.parse(rows[0].config) : (rows[0].config || {});
|
|
409
|
+
|
|
410
|
+
if (config.type === 'stdio') {
|
|
411
|
+
// Test stdio: spawn process, send initialize, read response
|
|
412
|
+
const { spawn } = await import('node:child_process');
|
|
413
|
+
const args = config.args || [];
|
|
414
|
+
const env = { ...process.env, ...(config.env || {}) };
|
|
415
|
+
const child = spawn(config.command, args, { stdio: ['pipe', 'pipe', 'pipe'], env, timeout: (config.timeout || 30) * 1000 });
|
|
416
|
+
|
|
417
|
+
const initMsg = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'AgenticMail', version: '1.0' } } });
|
|
418
|
+
child.stdin.write(initMsg + '\n');
|
|
419
|
+
|
|
420
|
+
const result: any = await new Promise((resolve) => {
|
|
421
|
+
let buf = '';
|
|
422
|
+
const timer = setTimeout(() => { child.kill(); resolve({ error: 'Timeout after ' + (config.timeout || 30) + 's' }); }, (config.timeout || 30) * 1000);
|
|
423
|
+
child.stdout.on('data', (chunk: Buffer) => {
|
|
424
|
+
buf += chunk.toString();
|
|
425
|
+
try {
|
|
426
|
+
const parsed = JSON.parse(buf.trim());
|
|
427
|
+
clearTimeout(timer);
|
|
428
|
+
child.kill();
|
|
429
|
+
resolve(parsed);
|
|
430
|
+
} catch { /* wait for more data */ }
|
|
431
|
+
});
|
|
432
|
+
child.on('error', (err: any) => { clearTimeout(timer); resolve({ error: err.message }); });
|
|
433
|
+
child.on('exit', (code: number) => { if (!buf) { clearTimeout(timer); resolve({ error: 'Process exited with code ' + code }); } });
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
if (result.error) return c.json({ error: typeof result.error === 'string' ? result.error : result.error.message || 'Unknown error' });
|
|
437
|
+
|
|
438
|
+
// Now list tools
|
|
439
|
+
const listMsg = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|
|
440
|
+
const child2 = spawn(config.command, args, { stdio: ['pipe', 'pipe', 'pipe'], env, timeout: 15000 });
|
|
441
|
+
child2.stdin.write(initMsg + '\n');
|
|
442
|
+
|
|
443
|
+
const tools: any[] = await new Promise((resolve) => {
|
|
444
|
+
let phase = 'init';
|
|
445
|
+
let buf2 = '';
|
|
446
|
+
const timer2 = setTimeout(() => { child2.kill(); resolve([]); }, 15000);
|
|
447
|
+
child2.stdout.on('data', (chunk: Buffer) => {
|
|
448
|
+
buf2 += chunk.toString();
|
|
449
|
+
const lines = buf2.split('\n');
|
|
450
|
+
for (const line of lines) {
|
|
451
|
+
if (!line.trim()) continue;
|
|
452
|
+
try {
|
|
453
|
+
const parsed = JSON.parse(line.trim());
|
|
454
|
+
if (phase === 'init' && parsed.id === 1) {
|
|
455
|
+
phase = 'tools';
|
|
456
|
+
buf2 = '';
|
|
457
|
+
child2.stdin.write(listMsg + '\n');
|
|
458
|
+
} else if (phase === 'tools' && parsed.id === 2) {
|
|
459
|
+
clearTimeout(timer2);
|
|
460
|
+
child2.kill();
|
|
461
|
+
resolve(parsed.result?.tools || []);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
} catch { /* partial line */ }
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
child2.on('error', () => { clearTimeout(timer2); resolve([]); });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Save discovered tools
|
|
471
|
+
await engineDb.exec(`UPDATE mcp_servers SET status = 'connected', tool_count = $1, tools = $2, updated_at = NOW() WHERE id = $3`,
|
|
472
|
+
[tools.length, JSON.stringify(tools.map((t: any) => ({ name: t.name, description: t.description }))), id]);
|
|
473
|
+
|
|
474
|
+
return c.json({ ok: true, tools: tools.length, serverInfo: result.result?.serverInfo });
|
|
475
|
+
|
|
476
|
+
} else {
|
|
477
|
+
// Test HTTP/SSE: send initialize via HTTP
|
|
478
|
+
const url = config.url;
|
|
479
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json', ...(config.headers || {}) };
|
|
480
|
+
if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;
|
|
481
|
+
|
|
482
|
+
const resp = await fetch(url, {
|
|
483
|
+
method: 'POST',
|
|
484
|
+
headers,
|
|
485
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'AgenticMail', version: '1.0' } } }),
|
|
486
|
+
signal: AbortSignal.timeout((config.timeout || 30) * 1000),
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (!resp.ok) return c.json({ error: `Server returned ${resp.status}` });
|
|
490
|
+
const data = await resp.json() as any;
|
|
491
|
+
if (data.error) return c.json({ error: data.error.message || 'Server error' });
|
|
492
|
+
|
|
493
|
+
// List tools
|
|
494
|
+
const toolResp = await fetch(url, {
|
|
495
|
+
method: 'POST', headers,
|
|
496
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }),
|
|
497
|
+
signal: AbortSignal.timeout(15000),
|
|
498
|
+
});
|
|
499
|
+
let tools: any[] = [];
|
|
500
|
+
if (toolResp.ok) {
|
|
501
|
+
const td = await toolResp.json() as any;
|
|
502
|
+
tools = td.result?.tools || [];
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
await engineDb.exec(`UPDATE mcp_servers SET status = 'connected', tool_count = $1, tools = $2, updated_at = NOW() WHERE id = $3`,
|
|
506
|
+
[tools.length, JSON.stringify(tools.map((t: any) => ({ name: t.name, description: t.description }))), id]);
|
|
507
|
+
|
|
508
|
+
return c.json({ ok: true, tools: tools.length, serverInfo: data.result?.serverInfo });
|
|
509
|
+
}
|
|
510
|
+
} catch (e: any) {
|
|
511
|
+
// Update status to error
|
|
512
|
+
try { await engineDb.exec(`UPDATE mcp_servers SET status = 'error', updated_at = NOW() WHERE id = $1`, [c.req.param('id')]); } catch {}
|
|
513
|
+
return c.json({ error: e.message });
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// ─── Integration Credentials Management ─────────────────
|
|
518
|
+
engine.put('/integrations/:skillId/credentials', async (c) => {
|
|
519
|
+
try {
|
|
520
|
+
const skillId = c.req.param('skillId');
|
|
521
|
+
const body = await c.req.json();
|
|
522
|
+
const orgId = c.req.query('orgId') || 'default';
|
|
523
|
+
// Store each credential field as a vault secret
|
|
524
|
+
for (const [key, value] of Object.entries(body)) {
|
|
525
|
+
if (!value) continue;
|
|
526
|
+
const secretName = `skill:${skillId}:${key}`;
|
|
527
|
+
// Check if exists, update or create
|
|
528
|
+
try {
|
|
529
|
+
const entries = await vault.getSecretsByOrg(orgId, 'skill_credential');
|
|
530
|
+
const existing = entries.find((e: any) => e.name === secretName);
|
|
531
|
+
if (existing) {
|
|
532
|
+
await vault.updateSecret(existing.id, { value: value as string });
|
|
533
|
+
} else {
|
|
534
|
+
await vault.storeSecret({ name: secretName, value: value as string, category: 'skill_credential', orgId });
|
|
535
|
+
}
|
|
536
|
+
} catch {
|
|
537
|
+
await vault.storeSecret({ name: secretName, value: value as string, category: 'skill_credential', orgId });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return c.json({ ok: true });
|
|
541
|
+
} catch (e: any) { return c.json({ error: e.message }, 500); }
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
engine.delete('/integrations/:skillId/credentials', async (c) => {
|
|
545
|
+
try {
|
|
546
|
+
const skillId = c.req.param('skillId');
|
|
547
|
+
const orgId = c.req.query('orgId') || 'default';
|
|
548
|
+
const entries = await vault.getSecretsByOrg(orgId, 'skill_credential');
|
|
549
|
+
const matching = entries.filter((e: any) => e.name?.startsWith(`skill:${skillId}:`));
|
|
550
|
+
for (const entry of matching) {
|
|
551
|
+
await vault.deleteSecret(entry.id);
|
|
552
|
+
}
|
|
553
|
+
return c.json({ ok: true });
|
|
554
|
+
} catch (e: any) { return c.json({ error: e.message }, 500); }
|
|
555
|
+
});
|
|
556
|
+
|
|
344
557
|
// ─── Integration catalog (serves all 144 MCP adapter integrations) ──
|
|
345
558
|
engine.get('/integrations/catalog', async (c) => {
|
|
346
559
|
try {
|