@agenticmail/enterprise 0.5.25 → 0.5.27
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-3SMTCIR4.js +220 -0
- package/dist/chunk-7XMV4YKZ.js +177 -0
- package/dist/chunk-P3TYMEZP.js +2113 -0
- package/dist/chunk-UT7KPNBZ.js +898 -0
- package/dist/cli.js +1 -1
- package/dist/dashboard/pages/agents.js +40 -6
- package/dist/index.js +4 -3
- package/dist/managed-GJK5VQMN.js +17 -0
- package/dist/server-4ERCDJOV.js +12 -0
- package/dist/setup-AYFBHCHB.js +20 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +158 -0
- package/src/dashboard/pages/agents.js +40 -6
package/dist/cli.js
CHANGED
|
@@ -31,7 +31,7 @@ export function DeployModal({ agentId, agentConfig, onClose, onDeployed, toast }
|
|
|
31
31
|
const doDeploy = async () => {
|
|
32
32
|
setError(''); setLoading(true);
|
|
33
33
|
try {
|
|
34
|
-
await
|
|
34
|
+
await apiCall('/agents/' + agentId + '/deploy', { method: 'POST', body: JSON.stringify({ targetType: targetType, credentialId: selectedCred || undefined, config: config, deployedBy: 'dashboard' }) });
|
|
35
35
|
if (toast) toast('Deployment started', 'success');
|
|
36
36
|
if (onDeployed) onDeployed();
|
|
37
37
|
onClose();
|
|
@@ -233,7 +233,7 @@ export function DeploymentProgress({ agentId, onComplete }) {
|
|
|
233
233
|
export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
234
234
|
const [step, setStep] = useState(0);
|
|
235
235
|
const steps = ['Role', 'Basics', 'Persona', 'Skills', 'Permissions', 'Deployment', 'Review'];
|
|
236
|
-
const [form, setForm] = useState({ name: '', email: '', role: 'assistant', description: '', personality: '', skills: [], preset: null, customTools: { allowed: [], blocked: [] }, deployTarget: '
|
|
236
|
+
const [form, setForm] = useState({ name: '', email: '', role: 'assistant', description: '', personality: '', skills: [], preset: null, customTools: { allowed: [], blocked: [] }, deployTarget: 'fly', knowledgeBases: [], provider: '', model: '', approvalRequired: true, soulId: null, avatar: null, gender: '', dateOfBirth: '', maritalStatus: '', culturalBackground: '', language: 'en-us', autoOnboard: true, maxRiskLevel: 'medium', blockedSideEffects: ['runs-code', 'deletes-data', 'financial', 'controls-device'], approvalForRiskLevels: ['high', 'critical'], approvalForSideEffects: ['sends-email', 'sends-message'], rateLimits: { toolCallsPerMinute: 30, toolCallsPerHour: 500, toolCallsPerDay: 5000, externalActionsPerHour: 50 }, constraints: { maxConcurrentTasks: 5, maxSessionDurationMinutes: 480, sandboxMode: false }, traits: { communication: 'direct', detail: 'detail-oriented', energy: 'calm', humor: 'warm', formality: 'adaptive', empathy: 'moderate', patience: 'patient', creativity: 'creative' } });
|
|
237
237
|
const [allSkills, setAllSkills] = useState({});
|
|
238
238
|
const [providers, setProviders] = useState([]);
|
|
239
239
|
const [providerModels, setProviderModels] = useState([]);
|
|
@@ -245,6 +245,38 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
245
245
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
246
246
|
const [loading, setLoading] = useState(false);
|
|
247
247
|
const [showSetupGuide, setShowSetupGuide] = useState(false);
|
|
248
|
+
const [draftSaved, setDraftSaved] = useState(false);
|
|
249
|
+
|
|
250
|
+
// Load draft from localStorage on mount
|
|
251
|
+
useEffect(function() {
|
|
252
|
+
try {
|
|
253
|
+
var draft = localStorage.getItem('em_agent_draft');
|
|
254
|
+
if (draft) {
|
|
255
|
+
var parsed = JSON.parse(draft);
|
|
256
|
+
setForm(function(f) { return Object.assign({}, f, parsed); });
|
|
257
|
+
if (parsed._step) setStep(parsed._step);
|
|
258
|
+
}
|
|
259
|
+
} catch {}
|
|
260
|
+
}, []);
|
|
261
|
+
|
|
262
|
+
// Save draft function
|
|
263
|
+
var saveDraft = useCallback(function() {
|
|
264
|
+
try {
|
|
265
|
+
localStorage.setItem('em_agent_draft', JSON.stringify(Object.assign({}, form, { _step: step })));
|
|
266
|
+
setDraftSaved(true);
|
|
267
|
+
setTimeout(function() { setDraftSaved(false); }, 2000);
|
|
268
|
+
} catch {}
|
|
269
|
+
}, [form, step]);
|
|
270
|
+
|
|
271
|
+
// Auto-save draft on step change
|
|
272
|
+
useEffect(function() {
|
|
273
|
+
if (form.name) {
|
|
274
|
+
try { localStorage.setItem('em_agent_draft', JSON.stringify(Object.assign({}, form, { _step: step }))); } catch {}
|
|
275
|
+
}
|
|
276
|
+
}, [step]);
|
|
277
|
+
|
|
278
|
+
// Clear draft after successful creation
|
|
279
|
+
var clearDraft = function() { try { localStorage.removeItem('em_agent_draft'); } catch {} };
|
|
248
280
|
const [setupChecked, setSetupChecked] = useState(false);
|
|
249
281
|
|
|
250
282
|
useEffect(() => {
|
|
@@ -455,6 +487,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
455
487
|
toast('Agent "' + form.name + '" created successfully', 'success');
|
|
456
488
|
}
|
|
457
489
|
|
|
490
|
+
clearDraft();
|
|
458
491
|
onCreated();
|
|
459
492
|
onClose();
|
|
460
493
|
} catch (err) { toast(err.message, 'error'); }
|
|
@@ -982,10 +1015,10 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
982
1015
|
h('h3', { style: { fontSize: 15, fontWeight: 700, marginBottom: 4 } }, 'Deployment Target'),
|
|
983
1016
|
h('p', { style: { color: 'var(--text-secondary)', marginBottom: 16, fontSize: 13 } }, 'Choose where and how this agent will run.'),
|
|
984
1017
|
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } },
|
|
985
|
-
['docker', 'vps', 'local'].map(t =>
|
|
1018
|
+
['fly', 'docker', 'railway', 'vps', 'local'].map(t =>
|
|
986
1019
|
h('div', { key: t, className: 'preset-card' + (form.deployTarget === t ? ' selected' : ''), onClick: () => set('deployTarget', t), style: { padding: '16px 18px' } },
|
|
987
|
-
h('h4', { style: { marginBottom: 6 } }, { docker: 'Docker Container', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[t]),
|
|
988
|
-
h('p', null, { docker: 'Run in an isolated Docker container with resource limits.
|
|
1020
|
+
h('h4', { style: { marginBottom: 6 } }, { fly: 'Fly.io', docker: 'Docker Container', railway: 'Railway', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[t]),
|
|
1021
|
+
h('p', null, { fly: 'Deploy to Fly.io with auto-scaling, TLS, and health checks. Recommended for production.', docker: 'Run in an isolated Docker container with resource limits.', railway: 'Deploy to Railway with zero-config builds and auto-deploys.', vps: 'Deploy to a VPS or dedicated server via SSH. Full control.', local: 'Run on the current machine. Best for development and testing.' }[t])
|
|
989
1022
|
)
|
|
990
1023
|
)
|
|
991
1024
|
)
|
|
@@ -1042,7 +1075,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
1042
1075
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Approvals'), h('span', null, form.approvalRequired ? 'Required (risk: ' + form.approvalForRiskLevels.join(', ') + ')' : 'Not required'),
|
|
1043
1076
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Rate Limits'), h('span', null, form.rateLimits.toolCallsPerMinute + '/min, ' + form.rateLimits.toolCallsPerHour + '/hr, ' + form.rateLimits.toolCallsPerDay + '/day'),
|
|
1044
1077
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Constraints'), h('span', null, form.constraints.maxConcurrentTasks + ' tasks, ' + form.constraints.maxSessionDurationMinutes + 'min max' + (form.constraints.sandboxMode ? ', sandbox' : '')),
|
|
1045
|
-
h('span', { style: { color: 'var(--text-muted)' } }, 'Deployment'), h('span', null, { docker: 'Docker Container', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[form.deployTarget]),
|
|
1078
|
+
h('span', { style: { color: 'var(--text-muted)' } }, 'Deployment'), h('span', null, { fly: 'Fly.io', docker: 'Docker Container', railway: 'Railway', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[form.deployTarget] || form.deployTarget),
|
|
1046
1079
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Onboarding'), h('span', null, form.autoOnboard ? h('span', { className: 'badge badge-success' }, 'Auto-start') : h('span', { className: 'badge badge-neutral' }, 'Manual'))
|
|
1047
1080
|
)
|
|
1048
1081
|
)
|
|
@@ -1069,6 +1102,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
1069
1102
|
h('div', { className: 'modal-footer' },
|
|
1070
1103
|
step > 0 && h('button', { className: 'btn btn-secondary', onClick: () => setStep(step - 1) }, 'Back'),
|
|
1071
1104
|
step === 0 && !form.soulId && h('button', { className: 'btn btn-ghost', onClick: () => setStep(1) }, 'Skip — Configure Manually'),
|
|
1105
|
+
h('button', { className: 'btn btn-ghost', onClick: saveDraft, style: { fontSize: 12 } }, draftSaved ? '\u2713 Draft Saved' : 'Save Draft'),
|
|
1072
1106
|
h('div', { style: { flex: 1 } }),
|
|
1073
1107
|
step < lastStep && h('button', { className: 'btn btn-primary', disabled: !canNext(), onClick: () => setStep(step + 1) }, 'Next'),
|
|
1074
1108
|
step === lastStep && h('button', { className: 'btn btn-primary', disabled: loading, onClick: doCreate }, loading ? 'Creating...' : 'Create Agent')
|
package/dist/index.js
CHANGED
|
@@ -50,11 +50,11 @@ import {
|
|
|
50
50
|
requireRole,
|
|
51
51
|
securityHeaders,
|
|
52
52
|
validate
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-P3TYMEZP.js";
|
|
54
54
|
import {
|
|
55
55
|
provision,
|
|
56
56
|
runSetupWizard
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-UT7KPNBZ.js";
|
|
58
58
|
import {
|
|
59
59
|
ENGINE_TABLES,
|
|
60
60
|
ENGINE_TABLES_POSTGRES,
|
|
@@ -70,7 +70,8 @@ import {
|
|
|
70
70
|
generateDockerCompose,
|
|
71
71
|
generateEnvFile,
|
|
72
72
|
generateFlyToml
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-7XMV4YKZ.js";
|
|
74
|
+
import "./chunk-3SMTCIR4.js";
|
|
74
75
|
import {
|
|
75
76
|
CircuitBreaker,
|
|
76
77
|
CircuitOpenError,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deployToCloud,
|
|
3
|
+
generateDockerCompose,
|
|
4
|
+
generateEnvFile,
|
|
5
|
+
generateFlyToml,
|
|
6
|
+
generateRailwayConfig
|
|
7
|
+
} from "./chunk-7XMV4YKZ.js";
|
|
8
|
+
import "./chunk-3SMTCIR4.js";
|
|
9
|
+
import "./chunk-JLSQOQ5L.js";
|
|
10
|
+
import "./chunk-KFQGP6VL.js";
|
|
11
|
+
export {
|
|
12
|
+
deployToCloud,
|
|
13
|
+
generateDockerCompose,
|
|
14
|
+
generateEnvFile,
|
|
15
|
+
generateFlyToml,
|
|
16
|
+
generateRailwayConfig
|
|
17
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createServer
|
|
3
|
+
} from "./chunk-P3TYMEZP.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-UT7KPNBZ.js";
|
|
10
|
+
import "./chunk-HEK7L3DT.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/admin/routes.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { AppEnv } from '../types/hono-env.js';
|
|
|
11
11
|
import type { DatabaseAdapter } from '../db/adapter.js';
|
|
12
12
|
import { validate, requireRole, ValidationError } from '../middleware/index.js';
|
|
13
13
|
import { PROVIDER_REGISTRY, type ProviderDef, type CustomProviderDef } from '../runtime/providers.js';
|
|
14
|
+
import { deployToFly, getAppStatus, destroyApp, type FlyConfig, type AppConfig } from '../deploy/fly.js';
|
|
14
15
|
|
|
15
16
|
export function createAdminRoutes(db: DatabaseAdapter) {
|
|
16
17
|
const api = new Hono<AppEnv>();
|
|
@@ -108,6 +109,163 @@ export function createAdminRoutes(db: DatabaseAdapter) {
|
|
|
108
109
|
return c.json({ ok: true });
|
|
109
110
|
});
|
|
110
111
|
|
|
112
|
+
// ─── Agent Deployment ─────────────────────────────────
|
|
113
|
+
|
|
114
|
+
api.post('/agents/:id/deploy', requireRole('admin'), async (c) => {
|
|
115
|
+
const agentId = c.req.param('id');
|
|
116
|
+
const agent = await db.getAgent(agentId);
|
|
117
|
+
if (!agent) return c.json({ error: 'Agent not found' }, 404);
|
|
118
|
+
|
|
119
|
+
const body = await c.req.json();
|
|
120
|
+
const targetType = body.targetType || 'fly';
|
|
121
|
+
const config = body.config || {};
|
|
122
|
+
|
|
123
|
+
// Get deployment credentials
|
|
124
|
+
const settings = await db.getSettings();
|
|
125
|
+
const pricingConfig = (settings as any)?.modelPricingConfig || {};
|
|
126
|
+
const providerApiKeys = pricingConfig.providerApiKeys || {};
|
|
127
|
+
|
|
128
|
+
if (targetType === 'fly') {
|
|
129
|
+
// Get Fly.io API token from deploy credentials or config
|
|
130
|
+
let flyToken = config.flyApiToken || process.env.FLY_API_TOKEN;
|
|
131
|
+
if (!flyToken && body.credentialId) {
|
|
132
|
+
// Look up stored credential
|
|
133
|
+
try {
|
|
134
|
+
const creds = await (db as any).query?.('SELECT config FROM deploy_credentials WHERE id = $1', [body.credentialId]);
|
|
135
|
+
if (creds?.rows?.[0]?.config) {
|
|
136
|
+
const credConfig = typeof creds.rows[0].config === 'string' ? JSON.parse(creds.rows[0].config) : creds.rows[0].config;
|
|
137
|
+
flyToken = credConfig.apiToken;
|
|
138
|
+
}
|
|
139
|
+
} catch { /* ignore */ }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!flyToken) {
|
|
143
|
+
return c.json({ error: 'Fly.io API token required. Add it in Settings → Deployments or pass flyApiToken in config.' }, 400);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const flyConfig: FlyConfig = {
|
|
147
|
+
apiToken: flyToken,
|
|
148
|
+
org: config.flyOrg || 'personal',
|
|
149
|
+
image: config.image || 'node:22-slim',
|
|
150
|
+
regions: config.regions || ['iad'],
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const agentName = (agent as any).name || agentId;
|
|
154
|
+
const appConfig: AppConfig = {
|
|
155
|
+
subdomain: agentName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
156
|
+
dbType: 'postgres',
|
|
157
|
+
dbConnectionString: process.env.DATABASE_URL || '',
|
|
158
|
+
jwtSecret: process.env.JWT_SECRET || 'agent-' + agentId,
|
|
159
|
+
smtpHost: (settings as any)?.smtpHost,
|
|
160
|
+
smtpPort: (settings as any)?.smtpPort,
|
|
161
|
+
smtpUser: (settings as any)?.smtpUser,
|
|
162
|
+
smtpPass: (settings as any)?.smtpPass,
|
|
163
|
+
memoryMb: config.memoryMb || 256,
|
|
164
|
+
cpuKind: config.cpuKind || 'shared',
|
|
165
|
+
cpus: config.cpus || 1,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await deployToFly(appConfig, flyConfig);
|
|
170
|
+
|
|
171
|
+
// Update agent record with deployment info (stored in metadata)
|
|
172
|
+
const existingAgent = await db.getAgent(agentId);
|
|
173
|
+
const existingMeta = (existingAgent as any)?.metadata || {};
|
|
174
|
+
await db.updateAgent(agentId, {
|
|
175
|
+
status: result.status === 'started' ? 'active' : 'error',
|
|
176
|
+
metadata: {
|
|
177
|
+
...existingMeta,
|
|
178
|
+
deployment: {
|
|
179
|
+
target: 'fly',
|
|
180
|
+
appName: result.appName,
|
|
181
|
+
url: result.url,
|
|
182
|
+
region: result.region,
|
|
183
|
+
machineId: result.machineId,
|
|
184
|
+
deployedAt: new Date().toISOString(),
|
|
185
|
+
deployedBy: body.deployedBy || 'dashboard',
|
|
186
|
+
status: result.status,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
} as any);
|
|
190
|
+
|
|
191
|
+
return c.json({
|
|
192
|
+
success: result.status === 'started',
|
|
193
|
+
deployment: result,
|
|
194
|
+
});
|
|
195
|
+
} catch (err: any) {
|
|
196
|
+
return c.json({ error: 'Deployment failed: ' + err.message }, 500);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (targetType === 'local') {
|
|
201
|
+
const existingAgent = await db.getAgent(agentId);
|
|
202
|
+
const existingMeta = (existingAgent as any)?.metadata || {};
|
|
203
|
+
await db.updateAgent(agentId, {
|
|
204
|
+
status: 'active',
|
|
205
|
+
metadata: {
|
|
206
|
+
...existingMeta,
|
|
207
|
+
deployment: {
|
|
208
|
+
target: 'local',
|
|
209
|
+
url: `http://localhost:${3000 + Math.floor(Math.random() * 1000)}`,
|
|
210
|
+
deployedAt: new Date().toISOString(),
|
|
211
|
+
deployedBy: body.deployedBy || 'dashboard',
|
|
212
|
+
status: 'started',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
} as any);
|
|
216
|
+
return c.json({ success: true, deployment: { status: 'started', target: 'local' } });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return c.json({ error: 'Unsupported deploy target: ' + targetType + '. Supported: fly, docker, vps, local' }, 400);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Get deployment status
|
|
223
|
+
api.get('/agents/:id/deploy', requireRole('admin'), async (c) => {
|
|
224
|
+
const agent = await db.getAgent(c.req.param('id'));
|
|
225
|
+
if (!agent) return c.json({ error: 'Agent not found' }, 404);
|
|
226
|
+
|
|
227
|
+
const meta = (agent as any).metadata || {};
|
|
228
|
+
const info = meta.deployment;
|
|
229
|
+
if (!info) return c.json({ deployed: false });
|
|
230
|
+
|
|
231
|
+
if (info.target === 'fly' && info.appName) {
|
|
232
|
+
const flyToken = process.env.FLY_API_TOKEN;
|
|
233
|
+
if (flyToken) {
|
|
234
|
+
try {
|
|
235
|
+
const status = await getAppStatus(info.appName, { apiToken: flyToken });
|
|
236
|
+
return c.json({ deployed: true, ...info, live: status });
|
|
237
|
+
} catch { /* fall through */ }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return c.json({ deployed: true, ...info });
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Destroy deployment
|
|
245
|
+
api.delete('/agents/:id/deploy', requireRole('admin'), async (c) => {
|
|
246
|
+
const agent = await db.getAgent(c.req.param('id'));
|
|
247
|
+
if (!agent) return c.json({ error: 'Agent not found' }, 404);
|
|
248
|
+
|
|
249
|
+
const meta = (agent as any).metadata || {};
|
|
250
|
+
const info = meta.deployment;
|
|
251
|
+
if (!info) return c.json({ error: 'Agent not deployed' }, 400);
|
|
252
|
+
|
|
253
|
+
if (info.target === 'fly' && info.appName) {
|
|
254
|
+
const flyToken = process.env.FLY_API_TOKEN;
|
|
255
|
+
if (flyToken) {
|
|
256
|
+
try {
|
|
257
|
+
await destroyApp(info.appName, { apiToken: flyToken });
|
|
258
|
+
} catch (err: any) {
|
|
259
|
+
return c.json({ error: 'Failed to destroy: ' + err.message }, 500);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
delete meta.deployment;
|
|
265
|
+
await db.updateAgent(c.req.param('id'), { status: 'inactive', metadata: meta } as any);
|
|
266
|
+
return c.json({ ok: true, message: 'Deployment destroyed' });
|
|
267
|
+
});
|
|
268
|
+
|
|
111
269
|
// ─── Users ──────────────────────────────────────────
|
|
112
270
|
|
|
113
271
|
api.get('/users', requireRole('admin'), async (c) => {
|
|
@@ -31,7 +31,7 @@ export function DeployModal({ agentId, agentConfig, onClose, onDeployed, toast }
|
|
|
31
31
|
const doDeploy = async () => {
|
|
32
32
|
setError(''); setLoading(true);
|
|
33
33
|
try {
|
|
34
|
-
await
|
|
34
|
+
await apiCall('/agents/' + agentId + '/deploy', { method: 'POST', body: JSON.stringify({ targetType: targetType, credentialId: selectedCred || undefined, config: config, deployedBy: 'dashboard' }) });
|
|
35
35
|
if (toast) toast('Deployment started', 'success');
|
|
36
36
|
if (onDeployed) onDeployed();
|
|
37
37
|
onClose();
|
|
@@ -233,7 +233,7 @@ export function DeploymentProgress({ agentId, onComplete }) {
|
|
|
233
233
|
export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
234
234
|
const [step, setStep] = useState(0);
|
|
235
235
|
const steps = ['Role', 'Basics', 'Persona', 'Skills', 'Permissions', 'Deployment', 'Review'];
|
|
236
|
-
const [form, setForm] = useState({ name: '', email: '', role: 'assistant', description: '', personality: '', skills: [], preset: null, customTools: { allowed: [], blocked: [] }, deployTarget: '
|
|
236
|
+
const [form, setForm] = useState({ name: '', email: '', role: 'assistant', description: '', personality: '', skills: [], preset: null, customTools: { allowed: [], blocked: [] }, deployTarget: 'fly', knowledgeBases: [], provider: '', model: '', approvalRequired: true, soulId: null, avatar: null, gender: '', dateOfBirth: '', maritalStatus: '', culturalBackground: '', language: 'en-us', autoOnboard: true, maxRiskLevel: 'medium', blockedSideEffects: ['runs-code', 'deletes-data', 'financial', 'controls-device'], approvalForRiskLevels: ['high', 'critical'], approvalForSideEffects: ['sends-email', 'sends-message'], rateLimits: { toolCallsPerMinute: 30, toolCallsPerHour: 500, toolCallsPerDay: 5000, externalActionsPerHour: 50 }, constraints: { maxConcurrentTasks: 5, maxSessionDurationMinutes: 480, sandboxMode: false }, traits: { communication: 'direct', detail: 'detail-oriented', energy: 'calm', humor: 'warm', formality: 'adaptive', empathy: 'moderate', patience: 'patient', creativity: 'creative' } });
|
|
237
237
|
const [allSkills, setAllSkills] = useState({});
|
|
238
238
|
const [providers, setProviders] = useState([]);
|
|
239
239
|
const [providerModels, setProviderModels] = useState([]);
|
|
@@ -245,6 +245,38 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
245
245
|
const [previewOpen, setPreviewOpen] = useState(false);
|
|
246
246
|
const [loading, setLoading] = useState(false);
|
|
247
247
|
const [showSetupGuide, setShowSetupGuide] = useState(false);
|
|
248
|
+
const [draftSaved, setDraftSaved] = useState(false);
|
|
249
|
+
|
|
250
|
+
// Load draft from localStorage on mount
|
|
251
|
+
useEffect(function() {
|
|
252
|
+
try {
|
|
253
|
+
var draft = localStorage.getItem('em_agent_draft');
|
|
254
|
+
if (draft) {
|
|
255
|
+
var parsed = JSON.parse(draft);
|
|
256
|
+
setForm(function(f) { return Object.assign({}, f, parsed); });
|
|
257
|
+
if (parsed._step) setStep(parsed._step);
|
|
258
|
+
}
|
|
259
|
+
} catch {}
|
|
260
|
+
}, []);
|
|
261
|
+
|
|
262
|
+
// Save draft function
|
|
263
|
+
var saveDraft = useCallback(function() {
|
|
264
|
+
try {
|
|
265
|
+
localStorage.setItem('em_agent_draft', JSON.stringify(Object.assign({}, form, { _step: step })));
|
|
266
|
+
setDraftSaved(true);
|
|
267
|
+
setTimeout(function() { setDraftSaved(false); }, 2000);
|
|
268
|
+
} catch {}
|
|
269
|
+
}, [form, step]);
|
|
270
|
+
|
|
271
|
+
// Auto-save draft on step change
|
|
272
|
+
useEffect(function() {
|
|
273
|
+
if (form.name) {
|
|
274
|
+
try { localStorage.setItem('em_agent_draft', JSON.stringify(Object.assign({}, form, { _step: step }))); } catch {}
|
|
275
|
+
}
|
|
276
|
+
}, [step]);
|
|
277
|
+
|
|
278
|
+
// Clear draft after successful creation
|
|
279
|
+
var clearDraft = function() { try { localStorage.removeItem('em_agent_draft'); } catch {} };
|
|
248
280
|
const [setupChecked, setSetupChecked] = useState(false);
|
|
249
281
|
|
|
250
282
|
useEffect(() => {
|
|
@@ -455,6 +487,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
455
487
|
toast('Agent "' + form.name + '" created successfully', 'success');
|
|
456
488
|
}
|
|
457
489
|
|
|
490
|
+
clearDraft();
|
|
458
491
|
onCreated();
|
|
459
492
|
onClose();
|
|
460
493
|
} catch (err) { toast(err.message, 'error'); }
|
|
@@ -982,10 +1015,10 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
982
1015
|
h('h3', { style: { fontSize: 15, fontWeight: 700, marginBottom: 4 } }, 'Deployment Target'),
|
|
983
1016
|
h('p', { style: { color: 'var(--text-secondary)', marginBottom: 16, fontSize: 13 } }, 'Choose where and how this agent will run.'),
|
|
984
1017
|
h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 } },
|
|
985
|
-
['docker', 'vps', 'local'].map(t =>
|
|
1018
|
+
['fly', 'docker', 'railway', 'vps', 'local'].map(t =>
|
|
986
1019
|
h('div', { key: t, className: 'preset-card' + (form.deployTarget === t ? ' selected' : ''), onClick: () => set('deployTarget', t), style: { padding: '16px 18px' } },
|
|
987
|
-
h('h4', { style: { marginBottom: 6 } }, { docker: 'Docker Container', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[t]),
|
|
988
|
-
h('p', null, { docker: 'Run in an isolated Docker container with resource limits.
|
|
1020
|
+
h('h4', { style: { marginBottom: 6 } }, { fly: 'Fly.io', docker: 'Docker Container', railway: 'Railway', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[t]),
|
|
1021
|
+
h('p', null, { fly: 'Deploy to Fly.io with auto-scaling, TLS, and health checks. Recommended for production.', docker: 'Run in an isolated Docker container with resource limits.', railway: 'Deploy to Railway with zero-config builds and auto-deploys.', vps: 'Deploy to a VPS or dedicated server via SSH. Full control.', local: 'Run on the current machine. Best for development and testing.' }[t])
|
|
989
1022
|
)
|
|
990
1023
|
)
|
|
991
1024
|
)
|
|
@@ -1042,7 +1075,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
1042
1075
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Approvals'), h('span', null, form.approvalRequired ? 'Required (risk: ' + form.approvalForRiskLevels.join(', ') + ')' : 'Not required'),
|
|
1043
1076
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Rate Limits'), h('span', null, form.rateLimits.toolCallsPerMinute + '/min, ' + form.rateLimits.toolCallsPerHour + '/hr, ' + form.rateLimits.toolCallsPerDay + '/day'),
|
|
1044
1077
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Constraints'), h('span', null, form.constraints.maxConcurrentTasks + ' tasks, ' + form.constraints.maxSessionDurationMinutes + 'min max' + (form.constraints.sandboxMode ? ', sandbox' : '')),
|
|
1045
|
-
h('span', { style: { color: 'var(--text-muted)' } }, 'Deployment'), h('span', null, { docker: 'Docker Container', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[form.deployTarget]),
|
|
1078
|
+
h('span', { style: { color: 'var(--text-muted)' } }, 'Deployment'), h('span', null, { fly: 'Fly.io', docker: 'Docker Container', railway: 'Railway', vps: 'VPS / Dedicated Server', local: 'Local Machine' }[form.deployTarget] || form.deployTarget),
|
|
1046
1079
|
h('span', { style: { color: 'var(--text-muted)' } }, 'Onboarding'), h('span', null, form.autoOnboard ? h('span', { className: 'badge badge-success' }, 'Auto-start') : h('span', { className: 'badge badge-neutral' }, 'Manual'))
|
|
1047
1080
|
)
|
|
1048
1081
|
)
|
|
@@ -1069,6 +1102,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
|
|
|
1069
1102
|
h('div', { className: 'modal-footer' },
|
|
1070
1103
|
step > 0 && h('button', { className: 'btn btn-secondary', onClick: () => setStep(step - 1) }, 'Back'),
|
|
1071
1104
|
step === 0 && !form.soulId && h('button', { className: 'btn btn-ghost', onClick: () => setStep(1) }, 'Skip — Configure Manually'),
|
|
1105
|
+
h('button', { className: 'btn btn-ghost', onClick: saveDraft, style: { fontSize: 12 } }, draftSaved ? '\u2713 Draft Saved' : 'Save Draft'),
|
|
1072
1106
|
h('div', { style: { flex: 1 } }),
|
|
1073
1107
|
step < lastStep && h('button', { className: 'btn btn-primary', disabled: !canNext(), onClick: () => setStep(step + 1) }, 'Next'),
|
|
1074
1108
|
step === lastStep && h('button', { className: 'btn btn-primary', disabled: loading, onClick: doCreate }, loading ? 'Creating...' : 'Create Agent')
|