@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.
- package/dist/chunk-3AJ7YYAA.js +16106 -0
- package/dist/chunk-7AXTPSIN.js +16106 -0
- package/dist/chunk-7RUXEES6.js +898 -0
- package/dist/chunk-HBZS3OKV.js +9255 -0
- package/dist/chunk-KIFKMDHY.js +2194 -0
- package/dist/chunk-MMSLIL7U.js +9288 -0
- package/dist/chunk-PC7IMFOA.js +2194 -0
- package/dist/chunk-TIV6GMQ5.js +898 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +4 -4
- package/dist/routes-A7ZRLWXZ.js +6938 -0
- package/dist/routes-XTQKNAQS.js +6938 -0
- package/dist/runtime-7XXST6JN.js +49 -0
- package/dist/runtime-Y7S2UQHD.js +49 -0
- package/dist/server-73PZ4CHC.js +12 -0
- package/dist/server-C7K5DKUM.js +12 -0
- package/dist/setup-CFXJICEP.js +20 -0
- package/dist/setup-IRQGAGRA.js +20 -0
- package/package.json +1 -1
- package/src/engine/deployer.ts +186 -61
- package/src/engine/lifecycle.ts +42 -0
|
@@ -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
package/src/engine/deployer.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
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
|
|
388
|
-
|
|
389
|
-
const
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
|
396
|
-
emit('configure', 'started', '
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
await
|
|
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
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
//
|
|
436
|
-
|
|
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:
|
|
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 || `
|
|
534
|
-
const
|
|
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
|
|
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:
|
|
543
|
-
healthStatus:
|
|
635
|
+
status: isRunning ? 'running' : state === 'stopped' ? 'stopped' : 'error',
|
|
636
|
+
healthStatus: isRunning ? 'healthy' : 'unhealthy',
|
|
544
637
|
endpoint: `https://${appName}.fly.dev`,
|
|
545
|
-
version:
|
|
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');
|
package/src/engine/lifecycle.ts
CHANGED
|
@@ -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 &&
|