@agenticmail/enterprise 0.5.280 → 0.5.282

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,45 @@
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-KPGXRGQ4.js";
18
+ import {
19
+ PROVIDER_REGISTRY,
20
+ listAllProviders,
21
+ resolveApiKeyForProvider,
22
+ resolveProvider
23
+ } from "./chunk-UF3ZJMJO.js";
24
+ import "./chunk-KFQGP6VL.js";
25
+ export {
26
+ AgentRuntime,
27
+ EmailChannel,
28
+ FollowUpScheduler,
29
+ PROVIDER_REGISTRY,
30
+ SessionManager,
31
+ SubAgentManager,
32
+ ToolRegistry,
33
+ callLLM,
34
+ createAgentRuntime,
35
+ createNoopHooks,
36
+ createRuntimeHooks,
37
+ estimateMessageTokens,
38
+ estimateTokens,
39
+ executeTool,
40
+ listAllProviders,
41
+ resolveApiKeyForProvider,
42
+ resolveProvider,
43
+ runAgentLoop,
44
+ toolsToDefinitions
45
+ };
@@ -0,0 +1,15 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-O3RXJSHR.js";
4
+ import "./chunk-OF4MUWWS.js";
5
+ import "./chunk-UF3ZJMJO.js";
6
+ import "./chunk-3OC6RH7W.js";
7
+ import "./chunk-2DDKGTD6.js";
8
+ import "./chunk-YVK6F5OD.js";
9
+ import "./chunk-MKRNEM5A.js";
10
+ import "./chunk-DRXMYYKN.js";
11
+ import "./chunk-6WSX7QXF.js";
12
+ import "./chunk-KFQGP6VL.js";
13
+ export {
14
+ createServer
15
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-P5EGIVSM.js";
10
+ import "./chunk-ULRBF2T7.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.280",
3
+ "version": "0.5.282",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -305,6 +305,17 @@ export async function createAllTools(options?: AllToolsOptions): Promise<AnyAgen
305
305
  var visualMemoryTools = createVisualMemoryTools(options || {});
306
306
  rawTools = rawTools.concat(visualMemoryTools as any);
307
307
 
308
+ // Enterprise Database Access tools (real multi-DB connections via DatabaseConnectionManager)
309
+ if ((options as any)?.databaseManager && options?.agentId) {
310
+ try {
311
+ const { createDatabaseTools: createDbAccessTools } = await import('../database-access/agent-tools.js');
312
+ const dbAccessTools = createDbAccessTools((options as any).databaseManager, options.agentId);
313
+ rawTools = rawTools.concat(dbAccessTools as any);
314
+ } catch (e: any) {
315
+ console.warn('[tools] Failed to load database access tools:', e.message);
316
+ }
317
+ }
318
+
308
319
  // Enterprise tools (7 skills)
309
320
  var enterpriseTools: AnyAgentTool[] = [
310
321
  ...createDatabaseTools(options),
@@ -128,6 +128,8 @@ export interface ToolCreationOptions {
128
128
  };
129
129
  /** Engine database reference for tools that need DB access */
130
130
  engineDb?: any;
131
+ /** Database Connection Manager for enterprise database access */
132
+ databaseManager?: any;
131
133
  /** Middleware configuration for cross-cutting concerns */
132
134
  middleware?: {
133
135
  audit?: {
package/src/cli-agent.ts CHANGED
@@ -586,6 +586,19 @@ export async function runAgent(_args: string[]) {
586
586
  console.warn(`[agent] MCP Process Manager init failed (non-fatal): ${e.message}`);
587
587
  }
588
588
 
589
+ // ─── Database Connection Manager ───────────────────
590
+ // Enables agents to query external databases they've been granted access to
591
+ try {
592
+ const { DatabaseConnectionManager } = await import('./database-access/connection-manager.js');
593
+ const vault = (runtime as any).config?.vault;
594
+ const dbManager = new DatabaseConnectionManager({ vault });
595
+ await dbManager.setDb(engineDb);
596
+ (runtime as any).config.databaseManager = dbManager;
597
+ console.log(`[agent] Database Connection Manager started`);
598
+ } catch (e: any) {
599
+ console.warn(`[agent] Database Connection Manager init failed (non-fatal): ${e.message}`);
600
+ }
601
+
589
602
  await runtime.start();
590
603
  const runtimeApp = runtime.getApp();
591
604
 
@@ -175,9 +175,12 @@ export function DatabaseAccessPage() {
175
175
 
176
176
  // Tabs
177
177
  h('div', { style: s.tabs },
178
- h('div', { style: tab === 'connections' ? s.tabActive : s.tab, onClick: function() { setTab('connections'); } }, 'Connections'),
179
- h('div', { style: tab === 'agents' ? s.tabActive : s.tab, onClick: function() { setTab('agents'); } }, 'Agent Access'),
180
- h('div', { style: tab === 'audit' ? s.tabActive : s.tab, onClick: function() { setTab('audit'); } }, 'Audit Log'),
178
+ h('div', { style: tab === 'connections' ? s.tabActive : s.tab, onClick: function() { setTab('connections'); } },
179
+ h('span', { style: css('display: inline-flex; align-items: center; gap: 6px;') }, h('span', { style: css('display: flex; transform: scale(0.7);') }, I.database()), 'Connections')),
180
+ h('div', { style: tab === 'agents' ? s.tabActive : s.tab, onClick: function() { setTab('agents'); } },
181
+ h('span', { style: css('display: inline-flex; align-items: center; gap: 6px;') }, h('span', { style: css('display: flex; transform: scale(0.7);') }, I.shield()), 'Agent Access')),
182
+ h('div', { style: tab === 'audit' ? s.tabActive : s.tab, onClick: function() { setTab('audit'); } },
183
+ h('span', { style: css('display: inline-flex; align-items: center; gap: 6px;') }, h('span', { style: css('display: flex; transform: scale(0.7);') }, I.audit()), 'Audit Log')),
181
184
  ),
182
185
 
183
186
  // Content
@@ -305,7 +308,45 @@ function AgentAccessTab(props) {
305
308
 
306
309
  // ─── Audit Tab ───────────────────────────────────────────────────────────────
307
310
 
311
+ var AUDIT_PAGE_SIZE = 15;
312
+
308
313
  function AuditTab(props) {
314
+ var [search, setSearch] = useState('');
315
+ var [opFilter, setOpFilter] = useState('all');
316
+ var [statusFilter, setStatusFilter] = useState('all');
317
+ var [agentFilter, setAgentFilter] = useState('all');
318
+ var [page, setPage] = useState(0);
319
+ var [expanded, setExpanded] = useState(null);
320
+
321
+ // Get unique agents from audit log
322
+ var agents = [];
323
+ var agentSet = {};
324
+ props.auditLog.forEach(function(e) {
325
+ var name = e.agent_name || e.agent_id;
326
+ if (name && !agentSet[name]) { agentSet[name] = true; agents.push(name); }
327
+ });
328
+
329
+ // Filter entries
330
+ var filtered = props.auditLog.filter(function(e) {
331
+ if (opFilter !== 'all' && e.operation !== opFilter) return false;
332
+ if (statusFilter === 'ok' && !e.success) return false;
333
+ if (statusFilter === 'fail' && e.success) return false;
334
+ if (agentFilter !== 'all' && (e.agent_name || e.agent_id) !== agentFilter) return false;
335
+ if (search) {
336
+ var q = search.toLowerCase();
337
+ var haystack = ((e.query || '') + ' ' + (e.agent_name || '') + ' ' + (e.connection_name || '') + ' ' + (e.error || '')).toLowerCase();
338
+ if (haystack.indexOf(q) < 0) return false;
339
+ }
340
+ return true;
341
+ });
342
+
343
+ var totalPages = Math.max(1, Math.ceil(filtered.length / AUDIT_PAGE_SIZE));
344
+ if (page >= totalPages) page = totalPages - 1;
345
+ var paged = filtered.slice(page * AUDIT_PAGE_SIZE, (page + 1) * AUDIT_PAGE_SIZE);
346
+
347
+ // Reset page when filters change
348
+ var resetPage = function() { setPage(0); };
349
+
309
350
  if (props.auditLog.length === 0) {
310
351
  return h('div', { style: s.emptyState },
311
352
  h('div', { style: s.emptyIcon }, I.audit()),
@@ -314,37 +355,114 @@ function AuditTab(props) {
314
355
  );
315
356
  }
316
357
 
317
- return h('div', { style: css('overflow-x: auto;') },
318
- h('table', { style: s.auditTable },
319
- h('thead', null, h('tr', null,
320
- h('th', { style: s.auditTh }, 'Time'),
321
- h('th', { style: s.auditTh }, 'Agent'),
322
- h('th', { style: s.auditTh }, 'Database'),
323
- h('th', { style: s.auditTh }, 'Op'),
324
- h('th', { style: s.auditTh }, 'Query'),
325
- h('th', { style: s.auditTh }, 'Rows'),
326
- h('th', { style: s.auditTh }, 'Time'),
327
- h('th', { style: s.auditTh }, 'Status'),
328
- )),
329
- h('tbody', null,
330
- props.auditLog.map(function(entry) {
331
- var opColor = entry.operation === 'read' ? 'var(--success)' : entry.operation === 'write' ? 'var(--warning)' : entry.operation === 'delete' ? 'var(--danger)' : 'var(--text-muted)';
332
- return h('tr', { key: entry.id },
333
- h('td', { style: s.auditTd }, new Date(entry.timestamp).toLocaleString()),
334
- h('td', { style: s.auditTd }, entry.agent_name || entry.agent_id?.slice(0, 8)),
335
- h('td', { style: s.auditTd }, entry.connection_name || entry.connection_id?.slice(0, 8)),
336
- h('td', { style: Object.assign({}, s.auditTd, { fontWeight: 600, color: opColor }) }, entry.operation),
337
- h('td', { style: Object.assign({}, s.auditTd, { fontFamily: 'monospace', fontSize: '11px', maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }) }, entry.query),
338
- h('td', { style: s.auditTd }, entry.rows_affected),
339
- h('td', { style: s.auditTd }, entry.execution_time_ms + 'ms'),
340
- h('td', { style: s.auditTd }, entry.success
341
- ? h('span', { style: Object.assign({}, s.badge, s.badgeActive) }, 'OK')
342
- : h('span', { style: Object.assign({}, s.badge, s.badgeError), title: entry.error }, 'FAIL')
343
- ),
344
- );
345
- })
358
+ var opColor = function(op) {
359
+ return op === 'read' ? 'var(--success)' : op === 'write' ? 'var(--warning)' : op === 'delete' ? 'var(--danger)' : op === 'schema' ? 'var(--accent)' : 'var(--text-muted)';
360
+ };
361
+
362
+ var filterBar = css('display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; align-items: center;');
363
+ var filterSelect = css('padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-secondary); color: var(--text-primary); font-size: 12px;');
364
+ var searchInput = css('padding: 6px 10px; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-primary); color: var(--text-primary); font-size: 12px; flex: 1; min-width: 180px;');
365
+ var countBadge = css('font-size: 11px; color: var(--text-muted); margin-left: auto; white-space: nowrap;');
366
+
367
+ return h('div', null,
368
+ // Filter bar
369
+ h('div', { style: filterBar },
370
+ h('div', { style: css('display: flex; align-items: center; gap: 4px; color: var(--text-muted);') },
371
+ h('span', { style: css('display: flex; transform: scale(0.65);') }, I.search()),
372
+ ),
373
+ h('input', { style: searchInput, placeholder: 'Search queries, agents, databases, errors...', value: search, onInput: function(e) { setSearch(e.target.value); resetPage(); } }),
374
+ h('select', { style: filterSelect, value: opFilter, onChange: function(e) { setOpFilter(e.target.value); resetPage(); } },
375
+ h('option', { value: 'all' }, 'All Operations'),
376
+ h('option', { value: 'read' }, 'Read'),
377
+ h('option', { value: 'write' }, 'Write'),
378
+ h('option', { value: 'delete' }, 'Delete'),
379
+ h('option', { value: 'schema' }, 'Schema'),
380
+ h('option', { value: 'execute' }, 'Execute'),
381
+ ),
382
+ h('select', { style: filterSelect, value: statusFilter, onChange: function(e) { setStatusFilter(e.target.value); resetPage(); } },
383
+ h('option', { value: 'all' }, 'All Status'),
384
+ h('option', { value: 'ok' }, 'Success'),
385
+ h('option', { value: 'fail' }, 'Failed'),
386
+ ),
387
+ agents.length > 1 && h('select', { style: filterSelect, value: agentFilter, onChange: function(e) { setAgentFilter(e.target.value); resetPage(); } },
388
+ h('option', { value: 'all' }, 'All Agents'),
389
+ agents.map(function(a) { return h('option', { key: a, value: a }, a); })
390
+ ),
391
+ h('span', { style: countBadge }, filtered.length + ' of ' + props.auditLog.length + ' entries'),
392
+ h('button', { style: Object.assign({}, s.btn, { padding: '4px 10px', fontSize: '11px' }), onClick: props.onRefresh }, 'Refresh'),
393
+ ),
394
+
395
+ // Table
396
+ h('div', { style: css('overflow-x: auto; border: 1px solid var(--border); border-radius: 8px;') },
397
+ h('table', { style: s.auditTable },
398
+ h('thead', null, h('tr', null,
399
+ h('th', { style: s.auditTh }, 'Time'),
400
+ h('th', { style: s.auditTh }, 'Agent'),
401
+ h('th', { style: s.auditTh }, 'Database'),
402
+ h('th', { style: s.auditTh }, 'Operation'),
403
+ h('th', { style: s.auditTh }, 'Query'),
404
+ h('th', { style: s.auditTh }, 'Rows'),
405
+ h('th', { style: s.auditTh }, 'Latency'),
406
+ h('th', { style: s.auditTh }, 'Status'),
407
+ )),
408
+ h('tbody', null,
409
+ paged.length === 0 && h('tr', null,
410
+ h('td', { colSpan: 8, style: Object.assign({}, s.auditTd, { textAlign: 'center', color: 'var(--text-muted)', padding: '24px' }) }, 'No entries match your filters')
411
+ ),
412
+ paged.map(function(entry) {
413
+ var isExpanded = expanded === entry.id;
414
+ return h(Fragment, { key: entry.id },
415
+ h('tr', { style: css('cursor: pointer; transition: background 0.1s;'), onClick: function() { setExpanded(isExpanded ? null : entry.id); } },
416
+ h('td', { style: s.auditTd }, new Date(entry.timestamp).toLocaleString()),
417
+ h('td', { style: Object.assign({}, s.auditTd, { fontWeight: 500 }) }, entry.agent_name || (entry.agent_id ? entry.agent_id.slice(0, 8) + '...' : '—')),
418
+ h('td', { style: s.auditTd }, entry.connection_name || (entry.connection_id ? entry.connection_id.slice(0, 8) + '...' : '—')),
419
+ h('td', { style: Object.assign({}, s.auditTd, { fontWeight: 600, color: opColor(entry.operation) }) }, entry.operation),
420
+ h('td', { style: Object.assign({}, s.auditTd, { fontFamily: 'monospace', fontSize: '11px', maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }) }, entry.query),
421
+ h('td', { style: Object.assign({}, s.auditTd, { textAlign: 'right' }) }, entry.rows_affected != null ? entry.rows_affected : '—'),
422
+ h('td', { style: Object.assign({}, s.auditTd, { textAlign: 'right', whiteSpace: 'nowrap' }) }, entry.execution_time_ms != null ? entry.execution_time_ms + 'ms' : '—'),
423
+ h('td', { style: s.auditTd }, entry.success
424
+ ? h('span', { style: Object.assign({}, s.badge, s.badgeActive) }, 'OK')
425
+ : h('span', { style: Object.assign({}, s.badge, s.badgeError) }, 'FAIL')
426
+ ),
427
+ ),
428
+ // Expanded row detail
429
+ isExpanded && h('tr', null,
430
+ h('td', { colSpan: 8, style: css('padding: 12px 16px; background: var(--bg-secondary); border-bottom: 1px solid var(--border);') },
431
+ h('div', { style: css('display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 12px;') },
432
+ h('div', null,
433
+ h('div', { style: css('font-weight: 600; margin-bottom: 4px; color: var(--text-muted);') }, 'Full Query'),
434
+ h('pre', { style: css('margin: 0; padding: 8px; background: var(--bg-primary); border-radius: 6px; overflow-x: auto; font-size: 11px; white-space: pre-wrap; word-break: break-all; max-height: 200px;') }, entry.query || '—'),
435
+ ),
436
+ h('div', null,
437
+ h('div', { style: css('font-weight: 600; margin-bottom: 4px; color: var(--text-muted);') }, 'Details'),
438
+ h('div', { style: css('display: flex; flex-direction: column; gap: 4px;') },
439
+ h('div', null, h('strong', null, 'Agent ID: '), entry.agent_id || '—'),
440
+ h('div', null, h('strong', null, 'Connection ID: '), entry.connection_id || '—'),
441
+ h('div', null, h('strong', null, 'Rows Affected: '), entry.rows_affected != null ? String(entry.rows_affected) : '—'),
442
+ h('div', null, h('strong', null, 'Execution Time: '), entry.execution_time_ms != null ? entry.execution_time_ms + 'ms' : '—'),
443
+ h('div', null, h('strong', null, 'IP: '), entry.ip_address || '—'),
444
+ !entry.success && entry.error && h('div', { style: css('margin-top: 4px; padding: 6px 8px; background: rgba(239,68,68,0.1); border-radius: 4px; color: var(--danger);') },
445
+ h('strong', null, 'Error: '), entry.error
446
+ ),
447
+ ),
448
+ ),
449
+ ),
450
+ ),
451
+ ),
452
+ );
453
+ })
454
+ ),
346
455
  ),
347
456
  ),
457
+
458
+ // Pagination
459
+ totalPages > 1 && h('div', { style: css('display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 16px;') },
460
+ h('button', { style: Object.assign({}, s.btn, { padding: '4px 10px', fontSize: '12px' }), disabled: page === 0, onClick: function() { setPage(0); } }, '«'),
461
+ h('button', { style: Object.assign({}, s.btn, { padding: '4px 10px', fontSize: '12px' }), disabled: page === 0, onClick: function() { setPage(page - 1); } }, '‹'),
462
+ h('span', { style: css('font-size: 12px; color: var(--text-secondary);') }, 'Page ' + (page + 1) + ' of ' + totalPages),
463
+ h('button', { style: Object.assign({}, s.btn, { padding: '4px 10px', fontSize: '12px' }), disabled: page >= totalPages - 1, onClick: function() { setPage(page + 1); } }, '›'),
464
+ h('button', { style: Object.assign({}, s.btn, { padding: '4px 10px', fontSize: '12px' }), disabled: page >= totalPages - 1, onClick: function() { setPage(totalPages - 1); } }, '»'),
465
+ ),
348
466
  );
349
467
  }
350
468
 
@@ -355,26 +473,63 @@ function AddConnectionModal(props) {
355
473
  var [dbType, setDbType] = useState('');
356
474
  var [form, setForm] = useState({ name: '', host: '', port: '', database: '', username: '', password: '', connectionString: '', ssl: false, description: '' });
357
475
  var [saving, setSaving] = useState(false);
476
+ var [testing, setTesting] = useState(false);
477
+ var [testResult, setTestResult] = useState(null); // { success, error, latencyMs }
358
478
 
359
- var set = function(key, val) { setForm(function(f) { var n = Object.assign({}, f); n[key] = val; return n; }); };
479
+ var set = function(key, val) { setForm(function(f) { var n = Object.assign({}, f); n[key] = val; return n; }); setTestResult(null); };
360
480
 
361
481
  var isConnString = form.connectionString.length > 0;
362
482
 
483
+ var buildBody = function() {
484
+ var body = { type: dbType, name: form.name || (ALL_DB_TYPES.find(function(t) { return t.value === dbType; })?.label + ' Connection'), description: form.description, status: 'inactive' };
485
+ if (isConnString) {
486
+ body.connectionString = form.connectionString;
487
+ } else {
488
+ body.host = form.host;
489
+ body.port = form.port ? parseInt(form.port) : undefined;
490
+ body.database = form.database;
491
+ body.username = form.username;
492
+ body.password = form.password;
493
+ body.ssl = form.ssl;
494
+ }
495
+ return body;
496
+ };
497
+
498
+ var testConnection = async function() {
499
+ setTesting(true);
500
+ setTestResult(null);
501
+ try {
502
+ var result = await engineCall('/database/connections/test', { method: 'POST', body: JSON.stringify(buildBody()) });
503
+ setTestResult(result);
504
+ } catch (e) {
505
+ setTestResult({ success: false, error: e.message || 'Connection test failed' });
506
+ }
507
+ setTesting(false);
508
+ };
509
+
363
510
  var save = async function() {
511
+ // Test connection first if not already tested successfully
512
+ if (!testResult || !testResult.success) {
513
+ setTesting(true);
514
+ setTestResult(null);
515
+ try {
516
+ var result = await engineCall('/database/connections/test', { method: 'POST', body: JSON.stringify(buildBody()) });
517
+ setTestResult(result);
518
+ if (!result.success) {
519
+ setTesting(false);
520
+ return; // Don't save if test fails
521
+ }
522
+ } catch (e) {
523
+ setTestResult({ success: false, error: e.message || 'Connection test failed' });
524
+ setTesting(false);
525
+ return;
526
+ }
527
+ setTesting(false);
528
+ }
529
+
364
530
  setSaving(true);
365
531
  try {
366
- var body = { type: dbType, name: form.name || (ALL_DB_TYPES.find(function(t) { return t.value === dbType; })?.label + ' Connection'), description: form.description, status: 'inactive' };
367
- if (isConnString) {
368
- body.connectionString = form.connectionString;
369
- } else {
370
- body.host = form.host;
371
- body.port = form.port ? parseInt(form.port) : undefined;
372
- body.database = form.database;
373
- body.username = form.username;
374
- body.password = form.password;
375
- body.ssl = form.ssl;
376
- }
377
- await engineCall('/database/connections', { method: 'POST', body: JSON.stringify(body) });
532
+ await engineCall('/database/connections', { method: 'POST', body: JSON.stringify(buildBody()) });
378
533
  props.onSave();
379
534
  props.onClose();
380
535
  } catch (e) { alert('Failed: ' + e.message); }
@@ -454,9 +609,21 @@ function AddConnectionModal(props) {
454
609
  h('div', { style: s.label }, 'Description (optional)'),
455
610
  h('input', { style: s.input, placeholder: 'What is this database used for?', value: form.description, onInput: function(e) { set('description', e.target.value); } }),
456
611
  ),
612
+ // Connection test result
613
+ testResult && h('div', { style: css('padding: 8px 12px; border-radius: 6px; font-size: 12px; ' + (testResult.success
614
+ ? 'background: rgba(21,128,61,0.1); color: var(--success); border: 1px solid rgba(21,128,61,0.3);'
615
+ : 'background: rgba(239,68,68,0.1); color: var(--danger); border: 1px solid rgba(239,68,68,0.3);')) },
616
+ testResult.success
617
+ ? 'Connection successful! (' + testResult.latencyMs + 'ms)'
618
+ : 'Connection failed: ' + (testResult.error || 'Unknown error'),
619
+ ),
620
+
457
621
  h('div', { style: css('display: flex; justify-content: space-between; margin-top: 8px;') },
458
622
  h('button', { style: s.btn, onClick: function() { setStep(1); } }, '← Back'),
459
- h('button', { style: s.btnPrimary, disabled: saving || (!isConnString && !form.host), onClick: save }, saving ? 'Saving...' : 'Add Connection'),
623
+ h('div', { style: css('display: flex; gap: 8px;') },
624
+ h('button', { style: s.btn, disabled: testing || saving || (!isConnString && !form.host), onClick: testConnection }, testing ? 'Testing...' : 'Test Connection'),
625
+ h('button', { style: s.btnPrimary, disabled: testing || saving || (!isConnString && !form.host), onClick: save }, saving ? 'Saving...' : testing ? 'Testing...' : 'Add Connection'),
626
+ ),
460
627
  ),
461
628
  ),
462
629
  ),
@@ -534,6 +534,34 @@ export class DatabaseConnectionManager {
534
534
  }
535
535
  }
536
536
 
537
+ /**
538
+ * Test connection parameters without saving — creates a temporary connection,
539
+ * pings it, and immediately destroys it.
540
+ */
541
+ async testConnectionParams(params: {
542
+ type: string; host?: string; port?: number; database?: string;
543
+ username?: string; password?: string; connectionString?: string; ssl?: boolean;
544
+ }): Promise<{ success: boolean; latencyMs: number; error?: string }> {
545
+ const startMs = Date.now();
546
+ const driver = this.drivers.get(params.type as any);
547
+ if (!driver) return { success: false, latencyMs: 0, error: `No driver for database type: ${params.type}` };
548
+
549
+ let connection: any;
550
+ try {
551
+ connection = await driver.connect(
552
+ { type: params.type as any, host: params.host, port: params.port, database: params.database, ssl: params.ssl } as any,
553
+ { password: params.password, connectionString: params.connectionString },
554
+ );
555
+ const alive = await connection.ping();
556
+ const latencyMs = Date.now() - startMs;
557
+ return { success: alive, latencyMs };
558
+ } catch (err: any) {
559
+ return { success: false, latencyMs: Date.now() - startMs, error: err.message };
560
+ } finally {
561
+ try { if (connection?.close) await connection.close(); } catch {}
562
+ }
563
+ }
564
+
537
565
  // ─── Pool Management ───────────────────────────────────────────────────────
538
566
 
539
567
  private async getPooledConnection(connectionId: string): Promise<DatabaseConnection> {
@@ -88,7 +88,29 @@ export function createDatabaseAccessRoutes(manager: DatabaseConnectionManager) {
88
88
  return c.json({ ok: true });
89
89
  });
90
90
 
91
- /** Test a database connection */
91
+ /** Test connection parameters before saving (no persistence) — must come before :id routes */
92
+ router.post('/connections/test', async (c) => {
93
+ const body = await c.req.json();
94
+ if (!body.type) return c.json({ error: 'type is required' }, 400);
95
+ const startMs = Date.now();
96
+ try {
97
+ const result = await manager.testConnectionParams({
98
+ type: body.type,
99
+ host: body.host,
100
+ port: body.port,
101
+ database: body.database,
102
+ username: body.username,
103
+ password: body.password,
104
+ connectionString: body.connectionString,
105
+ ssl: body.ssl,
106
+ });
107
+ return c.json(result);
108
+ } catch (e: any) {
109
+ return c.json({ success: false, latencyMs: Date.now() - startMs, error: e.message || 'Connection test failed' });
110
+ }
111
+ });
112
+
113
+ /** Test an existing database connection */
92
114
  router.post('/connections/:id/test', async (c) => {
93
115
  const result = await manager.testConnection(c.req.param('id'));
94
116
  return c.json(result);
@@ -228,6 +228,10 @@ export class AgentRuntime {
228
228
  if (this.config.mcpProcessManager) {
229
229
  base.mcpProcessManager = this.config.mcpProcessManager;
230
230
  }
231
+ // Database Connection Manager — enterprise database access tools
232
+ if (this.config.databaseManager) {
233
+ base.databaseManager = this.config.databaseManager;
234
+ }
231
235
  return base;
232
236
  }
233
237
 
@@ -140,6 +140,8 @@ export interface RuntimeConfig {
140
140
  permissionEngine?: any;
141
141
  /** MCP Process Manager for external MCP server tools */
142
142
  mcpProcessManager?: import('../engine/mcp-process-manager.js').McpProcessManager;
143
+ /** Database Connection Manager for enterprise database access tools */
144
+ databaseManager?: import('../database-access/connection-manager.js').DatabaseConnectionManager;
143
145
  /** Knowledge base engine for RAG search tools */
144
146
  knowledgeEngine?: any;
145
147
  /** Real-time agent status tracker */
package/src/server.ts CHANGED
@@ -355,7 +355,7 @@ export function createServer(config: ServerConfig): ServerInstance {
355
355
  };
356
356
  }
357
357
  } catch {}
358
- const { vault: vaultRef, permissionEngine: permRef } = await import('./engine/routes.js');
358
+ const { vault: vaultRef, permissionEngine: permRef, databaseManager: dbMgr } = await import('./engine/routes.js');
359
359
  const runtime = createAgentRuntime({
360
360
  engineDb,
361
361
  adminDb: config.db,
@@ -367,7 +367,8 @@ export function createServer(config: ServerConfig): ServerInstance {
367
367
  agentMemoryManager: agentMemoryMgr,
368
368
  vault: vaultRef,
369
369
  permissionEngine: permRef,
370
- });
370
+ databaseManager: dbMgr,
371
+ } as any);
371
372
  await runtime.start();
372
373
  const runtimeApp = runtime.getApp();
373
374
  if (runtimeApp) {
@@ -620,7 +621,7 @@ export function createServer(config: ServerConfig): ServerInstance {
620
621
  };
621
622
  }
622
623
  } catch {}
623
- const { vault: vaultRef2, permissionEngine: permRef2 } = await import('./engine/routes.js');
624
+ const { vault: vaultRef2, permissionEngine: permRef2, databaseManager: dbMgr2 } = await import('./engine/routes.js');
624
625
  const runtime = createAgentRuntime({
625
626
  engineDb,
626
627
  adminDb: config.db,
@@ -633,7 +634,8 @@ export function createServer(config: ServerConfig): ServerInstance {
633
634
  vault: vaultRef2,
634
635
  permissionEngine: permRef2,
635
636
  hierarchyManager: (await import('./engine/routes.js')).hierarchyManager ?? undefined,
636
- });
637
+ databaseManager: dbMgr2,
638
+ } as any);
637
639
  await runtime.start();
638
640
  const runtimeApp = runtime.getApp();
639
641
  if (runtimeApp) mountRuntimeApp(runtimeApp);