@agenticmail/enterprise 0.5.324 → 0.5.325
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-UF2RKKS2.js +510 -0
- package/dist/chunk-4DBWU3P5.js +4929 -0
- package/dist/chunk-VBTHTPZ6.js +26055 -0
- package/dist/chunk-WD72IOF2.js +5101 -0
- package/dist/chunk-ZGFDTW4H.js +1519 -0
- package/dist/cli-agent-ZIIFI77N.js +2473 -0
- package/dist/cli-serve-MLR4KAE2.js +260 -0
- package/dist/cli.js +3 -3
- package/dist/dashboard/docs/cluster.html +285 -0
- package/dist/dashboard/pages/cluster.js +400 -69
- package/dist/index.js +4 -4
- package/dist/routes-IGR6PZCA.js +92 -0
- package/dist/runtime-EAWOE6JZ.js +45 -0
- package/dist/server-B3VJ6MSA.js +28 -0
- package/dist/setup-5YRQUOW2.js +20 -0
- package/logs/cloudflared-error.log +2 -0
- package/logs/enterprise-out.log +1 -0
- package/package.json +1 -1
- package/src/dashboard/docs/cluster.html +285 -0
- package/src/dashboard/pages/cluster.js +400 -69
- package/src/engine/cluster.ts +14 -1
- package/src/engine/routes.ts +118 -0
- package/src/engine/NOTE.MD +0 -52
package/src/engine/routes.ts
CHANGED
|
@@ -343,6 +343,124 @@ engine.get('/cluster/best-node', (c) => {
|
|
|
343
343
|
const node = cluster.findBestNode(caps);
|
|
344
344
|
return node ? c.json(node) : c.json({ error: 'No suitable node available' }, 404);
|
|
345
345
|
});
|
|
346
|
+
engine.post('/cluster/test-connection', async (c) => {
|
|
347
|
+
const { host, port } = await c.req.json();
|
|
348
|
+
if (!host) return c.json({ error: 'host required' }, 400);
|
|
349
|
+
const p = port || 3101;
|
|
350
|
+
const start = Date.now();
|
|
351
|
+
try {
|
|
352
|
+
const ctrl = new AbortController();
|
|
353
|
+
const timeout = setTimeout(() => ctrl.abort(), 5000);
|
|
354
|
+
const res = await fetch(`http://${host}:${p}/health`, { signal: ctrl.signal }).catch(() => null);
|
|
355
|
+
clearTimeout(timeout);
|
|
356
|
+
if (res && res.ok) {
|
|
357
|
+
const data = await res.json().catch(() => ({}));
|
|
358
|
+
return c.json({ success: true, latencyMs: Date.now() - start, version: data.version, agentId: data.agentId });
|
|
359
|
+
}
|
|
360
|
+
// Try the enterprise status endpoint as fallback
|
|
361
|
+
const ctrl2 = new AbortController();
|
|
362
|
+
const timeout2 = setTimeout(() => ctrl2.abort(), 5000);
|
|
363
|
+
const res2 = await fetch(`http://${host}:${p}/api/status`, { signal: ctrl2.signal }).catch(() => null);
|
|
364
|
+
clearTimeout(timeout2);
|
|
365
|
+
if (res2 && res2.ok) {
|
|
366
|
+
return c.json({ success: true, latencyMs: Date.now() - start, version: 'enterprise' });
|
|
367
|
+
}
|
|
368
|
+
return c.json({ success: false, error: 'No response from ' + host + ':' + p, latencyMs: Date.now() - start });
|
|
369
|
+
} catch (e: any) {
|
|
370
|
+
return c.json({ success: false, error: e.message || 'Connection failed', latencyMs: Date.now() - start });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
engine.post('/cluster/deploy-via-ssh', async (c) => {
|
|
375
|
+
const body = await c.req.json();
|
|
376
|
+
if (!body.host) return c.json({ error: 'host required' }, 400);
|
|
377
|
+
// Use the deployment engine's SSH infrastructure
|
|
378
|
+
try {
|
|
379
|
+
const dbUrl = process.env.DATABASE_URL || '';
|
|
380
|
+
const enterpriseUrl = process.env.ENTERPRISE_URL || `http://${body.host}:${body.port || 3101}`;
|
|
381
|
+
const nodeId = body.name?.toLowerCase().replace(/[^a-z0-9-]/g, '-') || 'worker-' + body.host.replace(/\./g, '-');
|
|
382
|
+
|
|
383
|
+
// Build remote setup script
|
|
384
|
+
const script = [
|
|
385
|
+
'#!/bin/bash',
|
|
386
|
+
'set -e',
|
|
387
|
+
'export NVM_DIR="$HOME/.nvm"',
|
|
388
|
+
'[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"',
|
|
389
|
+
'',
|
|
390
|
+
'# Install Node.js if not present',
|
|
391
|
+
'if ! command -v node &> /dev/null; then',
|
|
392
|
+
' if command -v apt-get &> /dev/null; then',
|
|
393
|
+
' curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -',
|
|
394
|
+
' sudo apt-get install -y nodejs',
|
|
395
|
+
' elif command -v brew &> /dev/null; then',
|
|
396
|
+
' brew install node',
|
|
397
|
+
' fi',
|
|
398
|
+
'fi',
|
|
399
|
+
'',
|
|
400
|
+
'# Install PM2 and AgenticMail',
|
|
401
|
+
'npm install -g pm2 @agenticmail/enterprise',
|
|
402
|
+
'',
|
|
403
|
+
'# Write env file',
|
|
404
|
+
'mkdir -p ~/.agenticmail',
|
|
405
|
+
`cat > ~/.agenticmail/worker.env << 'ENVEOF'`,
|
|
406
|
+
`ENTERPRISE_URL=${enterpriseUrl}`,
|
|
407
|
+
`WORKER_NODE_ID=${nodeId}`,
|
|
408
|
+
`WORKER_NAME="${body.name || nodeId}"`,
|
|
409
|
+
`DATABASE_URL=${dbUrl}`,
|
|
410
|
+
`PORT=${body.port || 3101}`,
|
|
411
|
+
'LOG_LEVEL=warn',
|
|
412
|
+
'ENVEOF',
|
|
413
|
+
'',
|
|
414
|
+
'# Start agents via PM2',
|
|
415
|
+
...(body.agentIds || []).map((id: string, i: number) =>
|
|
416
|
+
`pm2 start "$(which agenticmail-enterprise || echo npx @agenticmail/enterprise) agent --id ${id}" --name "agent-${i}" --env ~/.agenticmail/worker.env`
|
|
417
|
+
),
|
|
418
|
+
'pm2 save',
|
|
419
|
+
'echo "DEPLOY_SUCCESS"',
|
|
420
|
+
].join('\n');
|
|
421
|
+
|
|
422
|
+
const { execSync } = await import('child_process');
|
|
423
|
+
const sshTarget = `${body.user || 'root'}@${body.host}`;
|
|
424
|
+
const keyOpt = body.privateKey ? `-i /tmp/_am_ssh_key_${Date.now()}` : '';
|
|
425
|
+
|
|
426
|
+
if (body.privateKey) {
|
|
427
|
+
const { writeFileSync, chmodSync } = await import('fs');
|
|
428
|
+
const keyPath = `/tmp/_am_ssh_key_${Date.now()}`;
|
|
429
|
+
writeFileSync(keyPath, body.privateKey, { mode: 0o600 });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const scriptB64 = Buffer.from(script).toString('base64');
|
|
433
|
+
const sshCmd = `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${keyOpt} ${sshTarget} "echo '${scriptB64}' | base64 -d | bash"`;
|
|
434
|
+
|
|
435
|
+
// Run async — don't block the request
|
|
436
|
+
const { exec } = await import('child_process');
|
|
437
|
+
exec(sshCmd, { timeout: 120_000 }, (err, stdout, stderr) => {
|
|
438
|
+
if (err) {
|
|
439
|
+
console.warn(`[cluster] SSH deploy to ${body.host} failed:`, err.message);
|
|
440
|
+
} else if (stdout.includes('DEPLOY_SUCCESS')) {
|
|
441
|
+
console.log(`[cluster] SSH deploy to ${body.host} succeeded`);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return c.json({ message: 'Deployment started via SSH to ' + sshTarget + '. Check the Cluster page for the node to appear.', nodeId });
|
|
446
|
+
} catch (e: any) {
|
|
447
|
+
return c.json({ error: 'SSH deploy failed: ' + e.message }, 500);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
engine.post('/cluster/nodes/:nodeId/restart', async (c) => {
|
|
452
|
+
const node = cluster.getNode(c.req.param('nodeId'));
|
|
453
|
+
if (!node) return c.json({ error: 'Node not found' }, 404);
|
|
454
|
+
try {
|
|
455
|
+
const ctrl = new AbortController();
|
|
456
|
+
setTimeout(() => ctrl.abort(), 10000);
|
|
457
|
+
await fetch(`${node.url}/restart`, { method: 'POST', signal: ctrl.signal }).catch(() => {});
|
|
458
|
+
return c.json({ ok: true, message: 'Restart signal sent to ' + node.name });
|
|
459
|
+
} catch (e: any) {
|
|
460
|
+
return c.json({ error: e.message }, 500);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
346
464
|
engine.get('/cluster/stream', (c) => {
|
|
347
465
|
const stream = new ReadableStream({
|
|
348
466
|
start(controller) {
|
package/src/engine/NOTE.MD
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
All done. Here's what I built:
|
|
2
|
-
|
|
3
|
-
### 1. Setup Wizard — Reverted (no LLM step)
|
|
4
|
-
|
|
5
|
-
Reverted back to original. Instead, SDKs auto-install during bootstrap — when an admin creates the first account, the server installs
|
|
6
|
-
@anthropic-ai/sdk, openai, and elevenlabs in the background.
|
|
7
|
-
|
|
8
|
-
### 2. Anthropic Token + API Key Support
|
|
9
|
-
|
|
10
|
-
The LLM client now supports two auth modes:
|
|
11
|
-
|
|
12
|
-
- api-key (default): Standard x-api-key header — for Anthropic API keys (sk-ant-...)
|
|
13
|
-
- token: Authorization: Bearer header — for Anthropic Max subscription tokens, OAuth tokens, and proxy tokens (like OpenClaw uses)
|
|
14
|
-
|
|
15
|
-
This flows through: ModelConfig.authMode → AgentConfig → agent-loop.ts → callLLM() → callAnthropic() → Anthropic SDK's authToken parameter.
|
|
16
|
-
|
|
17
|
-
You can also set a custom baseUrl to point to any proxy endpoint.
|
|
18
|
-
|
|
19
|
-
### 3. Meeting Voice (ElevenLabs + BlackHole)
|
|
20
|
-
|
|
21
|
-
Three new tools when meetings is enabled:
|
|
22
|
-
|
|
23
|
-
- meeting_speak — Converts text to speech via ElevenLabs, plays through BlackHole virtual mic. Meeting participants hear the agent speak.
|
|
24
|
-
- meeting_audio_setup — Checks if BlackHole, sox, and ElevenLabs are configured.
|
|
25
|
-
- meeting_voices — Lists available voices (13 built-in + custom from ElevenLabs account).
|
|
26
|
-
|
|
27
|
-
Architecture (works on any enterprise deployment):
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
Agent LLM → "meeting_speak" tool → ElevenLabs API → MP3 file
|
|
31
|
-
→ sox plays to BlackHole virtual device
|
|
32
|
-
→ Browser picks up BlackHole as microphone
|
|
33
|
-
→ Meeting participants hear the agent
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
To enable on Mac Mini, you need to run:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
brew install blackhole-2ch # Virtual audio driver (needs sudo)
|
|
40
|
-
brew install sox # Audio routing to BlackHole
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Then set ELEVENLABS_API_KEY in .env.fola.
|
|
44
|
-
|
|
45
|
-
### 4. PM2 Fix
|
|
46
|
-
|
|
47
|
-
Ran pm2 save after stopping Fola — this persists the stopped state to the PM2 dump file. The dashboard's stop button only updates DB state but
|
|
48
|
-
doesn't call pm2 stop. That's a separate fix needed in the deployer (the local stop handler is a no-op).
|
|
49
|
-
connected | idle
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|