@agenticmail/enterprise 0.5.97 → 0.5.99

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,49 @@
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-3AJ7YYAA.js";
18
+ import "./chunk-NRF3YRF7.js";
19
+ import "./chunk-TYW5XTOW.js";
20
+ import "./chunk-AQH4DFYV.js";
21
+ import "./chunk-JLSQOQ5L.js";
22
+ import {
23
+ PROVIDER_REGISTRY,
24
+ listAllProviders,
25
+ resolveApiKeyForProvider,
26
+ resolveProvider
27
+ } from "./chunk-67KZYSLU.js";
28
+ import "./chunk-KFQGP6VL.js";
29
+ export {
30
+ AgentRuntime,
31
+ EmailChannel,
32
+ FollowUpScheduler,
33
+ PROVIDER_REGISTRY,
34
+ SessionManager,
35
+ SubAgentManager,
36
+ ToolRegistry,
37
+ callLLM,
38
+ createAgentRuntime,
39
+ createNoopHooks,
40
+ createRuntimeHooks,
41
+ estimateMessageTokens,
42
+ estimateTokens,
43
+ executeTool,
44
+ listAllProviders,
45
+ resolveApiKeyForProvider,
46
+ resolveProvider,
47
+ runAgentLoop,
48
+ toolsToDefinitions
49
+ };
@@ -0,0 +1,49 @@
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-7AXTPSIN.js";
18
+ import "./chunk-NRF3YRF7.js";
19
+ import "./chunk-TYW5XTOW.js";
20
+ import "./chunk-AQH4DFYV.js";
21
+ import "./chunk-JLSQOQ5L.js";
22
+ import {
23
+ PROVIDER_REGISTRY,
24
+ listAllProviders,
25
+ resolveApiKeyForProvider,
26
+ resolveProvider
27
+ } from "./chunk-67KZYSLU.js";
28
+ import "./chunk-KFQGP6VL.js";
29
+ export {
30
+ AgentRuntime,
31
+ EmailChannel,
32
+ FollowUpScheduler,
33
+ PROVIDER_REGISTRY,
34
+ SessionManager,
35
+ SubAgentManager,
36
+ ToolRegistry,
37
+ callLLM,
38
+ createAgentRuntime,
39
+ createNoopHooks,
40
+ createRuntimeHooks,
41
+ estimateMessageTokens,
42
+ estimateTokens,
43
+ executeTool,
44
+ listAllProviders,
45
+ resolveApiKeyForProvider,
46
+ resolveProvider,
47
+ runAgentLoop,
48
+ toolsToDefinitions
49
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-KIFKMDHY.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-JLSQOQ5L.js";
6
+ import "./chunk-RO537U6H.js";
7
+ import "./chunk-DRXMYYKN.js";
8
+ import "./chunk-67KZYSLU.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-PC7IMFOA.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-JLSQOQ5L.js";
6
+ import "./chunk-RO537U6H.js";
7
+ import "./chunk-DRXMYYKN.js";
8
+ import "./chunk-67KZYSLU.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-TIV6GMQ5.js";
10
+ import "./chunk-QDXUZP7Y.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-7RUXEES6.js";
10
+ import "./chunk-QDXUZP7Y.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.97",
3
+ "version": "0.5.99",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -126,7 +126,7 @@ export class DeploymentEngine {
126
126
  case 'vps':
127
127
  return this.execSSH(config, `sudo systemctl stop agenticmail-${config.name}`);
128
128
  case 'fly':
129
- return this.execCommand(`fly apps destroy agenticmail-${config.name} --yes`);
129
+ return this.flyMachineAction(config, 'stop');
130
130
  case 'local':
131
131
  return { success: true, message: 'Local agent stopped' };
132
132
  default:
@@ -144,7 +144,7 @@ export class DeploymentEngine {
144
144
  case 'vps':
145
145
  return this.execSSH(config, `sudo systemctl restart agenticmail-${config.name}`);
146
146
  case 'fly':
147
- return this.execCommand(`fly apps restart agenticmail-${config.name}`);
147
+ return this.flyMachineAction(config, 'restart');
148
148
  case 'local':
149
149
  return { success: true, message: 'Local agent restarted' };
150
150
  default:
@@ -191,7 +191,7 @@ export class DeploymentEngine {
191
191
  case 'vps':
192
192
  return (await this.execSSH(config, `journalctl -u agenticmail-${config.name} --no-pager -n ${lines}`)).message;
193
193
  case 'fly':
194
- return (await this.execCommand(`fly logs -a agenticmail-${config.name} -n ${lines}`)).message;
194
+ return `Logs available at: https://fly.io/apps/${config.deployment.config.cloud?.appName || 'unknown'}/monitoring`;
195
195
  default:
196
196
  return 'Log streaming not supported for this target';
197
197
  }
@@ -382,67 +382,151 @@ export class DeploymentEngine {
382
382
  };
383
383
  }
384
384
 
385
+ /**
386
+ * Deploy agent to Fly.io using the Machines API (HTTP).
387
+ * No flyctl CLI needed — works from inside containers.
388
+ *
389
+ * Flow:
390
+ * 1. Create app (if it doesn't exist)
391
+ * 2. Create a Machine with the @agenticmail/enterprise Docker image
392
+ * 3. Set secrets (API keys, DB URL, etc.)
393
+ * 4. Wait for machine to start
394
+ */
385
395
  private async deployFly(config: AgentConfig, emit: Function): Promise<DeploymentResult> {
386
396
  const cloud = config.deployment.config.cloud;
387
- if (!cloud || cloud.provider !== 'fly') throw new Error('Fly.io config missing');
388
-
389
- const appName = cloud.appName || `agenticmail-${config.name}`;
397
+ if (!cloud) throw new Error('Fly.io config missing');
398
+
399
+ const apiToken = cloud.apiToken;
400
+ if (!apiToken) throw new Error('Fly.io API token is required');
401
+
402
+ const appName = cloud.appName || `am-agent-${config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 30)}`;
403
+ const region = cloud.region || 'iad';
404
+ const size = cloud.size || 'shared-cpu-1x';
405
+ const FLY_API = 'https://api.machines.dev/v1';
406
+
407
+ const flyFetch = async (path: string, method: string = 'GET', body?: any): Promise<any> => {
408
+ const res = await fetch(`${FLY_API}${path}`, {
409
+ method,
410
+ headers: {
411
+ 'Authorization': `Bearer ${apiToken}`,
412
+ 'Content-Type': 'application/json',
413
+ },
414
+ body: body ? JSON.stringify(body) : undefined,
415
+ });
416
+ const text = await res.text();
417
+ let data;
418
+ try { data = JSON.parse(text); } catch { data = { raw: text }; }
419
+ if (!res.ok) throw new Error(`Fly API ${method} ${path}: ${res.status} — ${data.error || text}`);
420
+ return data;
421
+ };
390
422
 
391
- emit('provision', 'started', `Creating Fly.io app ${appName}...`);
392
- await this.execCommand(`fly apps create ${appName} --org personal`, { FLY_API_TOKEN: cloud.apiToken });
393
- emit('provision', 'completed', `App ${appName} created`);
423
+ // 1. Create app (ignore "already exists" errors)
424
+ emit('provision', 'started', `Creating Fly.io app "${appName}"...`);
425
+ try {
426
+ await flyFetch('/apps', 'POST', {
427
+ app_name: appName,
428
+ org_slug: cloud.org || 'personal',
429
+ });
430
+ emit('provision', 'completed', `App "${appName}" created`);
431
+ } catch (e: any) {
432
+ if (e.message.includes('already exists')) {
433
+ emit('provision', 'completed', `App "${appName}" already exists, reusing`);
434
+ } else {
435
+ throw e;
436
+ }
437
+ }
394
438
 
395
- // Generate Dockerfile
396
- emit('configure', 'started', 'Generating Dockerfile...');
397
- const dockerfile = this.generateDockerfile(config);
439
+ // 2. Generate workspace files and encode as base64 init script
440
+ emit('configure', 'started', 'Preparing agent configuration...');
398
441
  const workspace = this.configGen.generateWorkspace(config);
442
+ const initCommands = Object.entries(workspace).map(([file, content]) => {
443
+ const b64 = Buffer.from(content).toString('base64');
444
+ return `echo "${b64}" | base64 -d > /workspace/${file}`;
445
+ }).join(' && ');
446
+
447
+ // Build environment variables for the agent
448
+ const env: Record<string, string> = {
449
+ NODE_ENV: 'production',
450
+ AGENTICMAIL_MODEL: `${config.model.provider}/${config.model.modelId}`,
451
+ AGENTICMAIL_AGENT_ID: config.id,
452
+ AGENTICMAIL_AGENT_NAME: config.displayName || config.name,
453
+ PORT: '3000',
454
+ };
455
+ if (config.model.thinkingLevel) env.AGENTICMAIL_THINKING = config.model.thinkingLevel;
456
+ emit('configure', 'completed', 'Configuration ready');
457
+
458
+ // 3. Check for existing machines — update if found, create if not
459
+ emit('install', 'started', 'Deploying machine...');
460
+ const existingMachines = await flyFetch(`/apps/${appName}/machines`);
461
+ const machineConfig = {
462
+ image: 'node:22-slim',
463
+ env,
464
+ services: [{
465
+ ports: [
466
+ { port: 443, handlers: ['tls', 'http'], force_https: true },
467
+ { port: 80, handlers: ['http'] },
468
+ ],
469
+ protocol: 'tcp',
470
+ internal_port: 3000,
471
+ }],
472
+ guest: {
473
+ cpu_kind: size.includes('performance') ? 'performance' : 'shared',
474
+ cpus: size.includes('2x') ? 2 : 1,
475
+ memory_mb: size.includes('2x') ? 1024 : 512,
476
+ },
477
+ init: {
478
+ cmd: ['sh', '-c', `mkdir -p /workspace && ${initCommands || 'true'} && npx @agenticmail/enterprise start`],
479
+ },
480
+ auto_destroy: false,
481
+ restart: { policy: 'always' },
482
+ };
399
483
 
400
- // Write temp build context
401
- const buildDir = `/tmp/agenticmail-build-${config.name}`;
402
- await this.execCommand(`mkdir -p ${buildDir}/workspace`);
403
- await this.writeFile(`${buildDir}/Dockerfile`, dockerfile);
404
- for (const [file, content] of Object.entries(workspace)) {
405
- await this.writeFile(`${buildDir}/workspace/${file}`, content);
484
+ let machineId: string;
485
+ if (existingMachines.length > 0) {
486
+ // Update existing machine
487
+ const existing = existingMachines[0];
488
+ machineId = existing.id;
489
+ await flyFetch(`/apps/${appName}/machines/${machineId}`, 'POST', {
490
+ config: machineConfig,
491
+ region,
492
+ });
493
+ emit('install', 'completed', `Machine ${machineId} updated`);
494
+ } else {
495
+ // Create new machine
496
+ const machine = await flyFetch(`/apps/${appName}/machines`, 'POST', {
497
+ name: `agent-${config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 20)}`,
498
+ region,
499
+ config: machineConfig,
500
+ });
501
+ machineId = machine.id;
502
+ emit('install', 'completed', `Machine ${machineId} created in ${region}`);
406
503
  }
407
504
 
408
- // Write fly.toml
409
- const flyToml = `
410
- app = "${appName}"
411
- primary_region = "${cloud.region || 'iad'}"
412
-
413
- [build]
414
- dockerfile = "Dockerfile"
415
-
416
- [http_service]
417
- internal_port = 3000
418
- force_https = true
419
- auto_stop_machines = true
420
- auto_start_machines = true
421
- min_machines_running = 1
422
-
423
- [[vm]]
424
- size = "${cloud.size || 'shared-cpu-1x'}"
425
- memory = "512mb"
426
- `;
427
- await this.writeFile(`${buildDir}/fly.toml`, flyToml);
428
- emit('configure', 'completed', 'Build context ready');
429
-
430
- // Deploy
431
- emit('install', 'started', 'Deploying to Fly.io (building + pushing)...');
432
- const deployResult = await this.execCommand(`cd ${buildDir} && fly deploy --now`, { FLY_API_TOKEN: cloud.apiToken });
433
- emit('install', deployResult.success ? 'completed' : 'failed', deployResult.message);
505
+ // 4. Wait for machine to start
506
+ emit('start', 'started', 'Waiting for machine to start...');
507
+ try {
508
+ await flyFetch(`/apps/${appName}/machines/${machineId}/wait?state=started&timeout=60`);
509
+ emit('start', 'completed', 'Machine is running');
510
+ } catch {
511
+ emit('start', 'completed', 'Machine starting (health check pending)');
512
+ }
434
513
 
435
- // Cleanup
436
- await this.execCommand(`rm -rf ${buildDir}`);
514
+ // Store deployment metadata
515
+ config.deployment.config.cloud = {
516
+ ...cloud,
517
+ provider: 'fly',
518
+ appName,
519
+ region,
520
+ size,
521
+ };
522
+ (config.deployment.config as any).flyMachineId = machineId;
523
+ (config.deployment.config as any).deployedAt = new Date().toISOString();
437
524
 
438
525
  const url = cloud.customDomain || `https://${appName}.fly.dev`;
439
-
440
- if (deployResult.success) {
441
- emit('complete', 'completed', `Agent live at ${url}`);
442
- }
526
+ emit('complete', 'completed', `Agent live at ${url}`);
443
527
 
444
528
  return {
445
- success: deployResult.success,
529
+ success: true,
446
530
  url,
447
531
  appId: appName,
448
532
  events: [],
@@ -528,29 +612,70 @@ primary_region = "${cloud.region || 'iad'}"
528
612
 
529
613
  private async getCloudStatus(config: AgentConfig, base: LiveAgentStatus): Promise<LiveAgentStatus> {
530
614
  const cloud = config.deployment.config.cloud;
531
- if (!cloud) return base;
615
+ if (!cloud || !cloud.apiToken) return base;
532
616
 
533
- const appName = cloud.appName || `agenticmail-${config.name}`;
534
- const result = await this.execCommand(`fly status -a ${appName} --json`, { FLY_API_TOKEN: cloud.apiToken });
535
-
536
- if (!result.success) return { ...base, status: 'error' };
617
+ const appName = cloud.appName || `am-agent-${config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 30)}`;
618
+ const FLY_API = 'https://api.machines.dev/v1';
537
619
 
538
620
  try {
539
- const status = JSON.parse(result.message);
621
+ const res = await fetch(`${FLY_API}/apps/${appName}/machines`, {
622
+ headers: { 'Authorization': `Bearer ${cloud.apiToken}` },
623
+ });
624
+ if (!res.ok) return { ...base, status: 'error', healthStatus: 'unhealthy' };
625
+
626
+ const machines = await res.json() as any[];
627
+ if (machines.length === 0) return { ...base, status: 'stopped' };
628
+
629
+ const machine = machines[0];
630
+ const state = machine.state;
631
+ const isRunning = state === 'started' || state === 'replacing';
632
+
540
633
  return {
541
634
  ...base,
542
- status: status.Deployed ? 'running' : 'stopped',
543
- healthStatus: status.Deployed ? 'healthy' : 'unhealthy',
635
+ status: isRunning ? 'running' : state === 'stopped' ? 'stopped' : 'error',
636
+ healthStatus: isRunning ? 'healthy' : 'unhealthy',
544
637
  endpoint: `https://${appName}.fly.dev`,
545
- version: status.Version?.toString(),
638
+ version: machine.image_ref?.tag,
639
+ uptime: machine.created_at ? Math.floor((Date.now() - new Date(machine.created_at).getTime()) / 1000) : undefined,
546
640
  };
547
641
  } catch {
548
- return { ...base, status: 'error' };
642
+ return { ...base, status: 'error', healthStatus: 'unhealthy' };
549
643
  }
550
644
  }
551
645
 
552
646
  // ─── Helpers ──────────────────────────────────────────
553
647
 
648
+ /** Stop or restart a Fly.io machine via the Machines API */
649
+ private async flyMachineAction(config: AgentConfig, action: 'stop' | 'restart'): Promise<{ success: boolean; message: string }> {
650
+ const cloud = config.deployment.config.cloud;
651
+ if (!cloud || !cloud.apiToken) return { success: false, message: 'Fly.io config missing' };
652
+
653
+ const appName = cloud.appName || `am-agent-${config.name.toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 30)}`;
654
+ const FLY_API = 'https://api.machines.dev/v1';
655
+
656
+ try {
657
+ const res = await fetch(`${FLY_API}/apps/${appName}/machines`, {
658
+ headers: { 'Authorization': `Bearer ${cloud.apiToken}` },
659
+ });
660
+ if (!res.ok) return { success: false, message: `Failed to list machines: ${res.status}` };
661
+ const machines = await res.json() as any[];
662
+ if (machines.length === 0) return { success: false, message: 'No machines found' };
663
+
664
+ const machineId = machines[0].id;
665
+ const actionRes = await fetch(`${FLY_API}/apps/${appName}/machines/${machineId}/${action}`, {
666
+ method: 'POST',
667
+ headers: { 'Authorization': `Bearer ${cloud.apiToken}` },
668
+ });
669
+ if (!actionRes.ok) {
670
+ const err = await actionRes.text();
671
+ return { success: false, message: `${action} failed: ${err}` };
672
+ }
673
+ return { success: true, message: `Machine ${machineId} ${action}ed` };
674
+ } catch (e: any) {
675
+ return { success: false, message: e.message };
676
+ }
677
+ }
678
+
554
679
  private validateConfig(config: AgentConfig) {
555
680
  if (!config.name) throw new Error('Agent name is required');
556
681
  if (!config.identity.role) throw new Error('Agent role is required');
@@ -309,6 +309,9 @@ export class AgentLifecycleManager {
309
309
  await this.persistAgent(agent);
310
310
 
311
311
  try {
312
+ // Resolve org-level deploy credentials if agent doesn't have its own
313
+ await this.resolveDeployCredentials(agent);
314
+
312
315
  // Run deployment
313
316
  this.transition(agent, 'deploying', 'Pushing configuration', 'system');
314
317
 
@@ -916,6 +919,45 @@ export class AgentLifecycleManager {
916
919
  });
917
920
  }
918
921
 
922
+ /**
923
+ * Resolve org-level deploy credentials and merge into agent config.
924
+ * If the agent's deployment config is missing an API token, look up
925
+ * the org's deploy_credentials table for a matching target type.
926
+ */
927
+ private async resolveDeployCredentials(agent: ManagedAgent): Promise<void> {
928
+ const target = agent.config?.deployment?.target;
929
+ if (!target) return;
930
+
931
+ // Ensure deployment.config exists
932
+ if (!agent.config.deployment.config) agent.config.deployment.config = {} as any;
933
+
934
+ // For fly/railway, check if cloud.apiToken is already set
935
+ if (target === 'fly' || target === 'railway') {
936
+ const cloud = agent.config.deployment.config.cloud;
937
+ if (cloud?.apiToken) return; // Agent has its own token
938
+
939
+ // Look up org-level credentials
940
+ if (this.engineDb) {
941
+ try {
942
+ const orgId = agent.orgId || 'default';
943
+ const creds = await this.engineDb.getDeployCredentialsByType(orgId, target);
944
+ if (creds.length > 0) {
945
+ const orgCred = creds[0];
946
+ agent.config.deployment.config.cloud = {
947
+ ...agent.config.deployment.config.cloud,
948
+ provider: target,
949
+ apiToken: orgCred.config?.apiToken || orgCred.config?.token,
950
+ ...(orgCred.config?.region && { region: orgCred.config.region }),
951
+ ...(orgCred.config?.org && { org: orgCred.config.org }),
952
+ };
953
+ }
954
+ } catch (err) {
955
+ console.error('[lifecycle] Failed to resolve deploy credentials:', err);
956
+ }
957
+ }
958
+ }
959
+ }
960
+
919
961
  private isConfigComplete(config: AgentConfig): boolean {
920
962
  return !!(
921
963
  config.name &&