@agenticmail/enterprise 0.5.72 → 0.5.74

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,47 @@
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-WBHMHTF3.js";
18
+ import "./chunk-TYW5XTOW.js";
19
+ import "./chunk-JLSQOQ5L.js";
20
+ import {
21
+ PROVIDER_REGISTRY,
22
+ listAllProviders,
23
+ resolveApiKeyForProvider,
24
+ resolveProvider
25
+ } from "./chunk-67KZYSLU.js";
26
+ import "./chunk-KFQGP6VL.js";
27
+ export {
28
+ AgentRuntime,
29
+ EmailChannel,
30
+ FollowUpScheduler,
31
+ PROVIDER_REGISTRY,
32
+ SessionManager,
33
+ SubAgentManager,
34
+ ToolRegistry,
35
+ callLLM,
36
+ createAgentRuntime,
37
+ createNoopHooks,
38
+ createRuntimeHooks,
39
+ estimateMessageTokens,
40
+ estimateTokens,
41
+ executeTool,
42
+ listAllProviders,
43
+ resolveApiKeyForProvider,
44
+ resolveProvider,
45
+ runAgentLoop,
46
+ toolsToDefinitions
47
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-KQCDQUAK.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-G7BU3BUW.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.72",
3
+ "version": "0.5.74",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -602,6 +602,49 @@ export function createAdminRoutes(db: DatabaseAdapter) {
602
602
  }
603
603
  });
604
604
 
605
+ // ─── Organization Email Config ─────────────────────
606
+
607
+ api.get('/settings/org-email', requireRole('admin'), async (c) => {
608
+ const settings = await db.getSettings();
609
+ const cfg = settings?.orgEmailConfig;
610
+ if (!cfg) return c.json({ configured: false });
611
+ return c.json({
612
+ configured: cfg.configured || false,
613
+ provider: cfg.provider,
614
+ label: cfg.label,
615
+ oauthClientId: cfg.oauthClientId,
616
+ oauthTenantId: cfg.oauthTenantId,
617
+ });
618
+ });
619
+
620
+ api.put('/settings/org-email', requireRole('admin'), async (c) => {
621
+ const body = await c.req.json();
622
+ const { provider, oauthClientId, oauthClientSecret, oauthTenantId } = body;
623
+ if (!provider || !['google', 'microsoft'].includes(provider)) {
624
+ return c.json({ error: 'provider must be "google" or "microsoft"' }, 400);
625
+ }
626
+ if (!oauthClientId || !oauthClientSecret) {
627
+ return c.json({ error: 'oauthClientId and oauthClientSecret are required' }, 400);
628
+ }
629
+ const label = provider === 'google' ? 'Google Workspace' : 'Microsoft 365';
630
+ const orgEmailConfig = {
631
+ provider,
632
+ oauthClientId,
633
+ oauthClientSecret,
634
+ oauthTenantId: provider === 'microsoft' ? (oauthTenantId || 'common') : undefined,
635
+ oauthRedirectUri: '', // Will be set per-agent at OAuth time
636
+ configured: true,
637
+ label,
638
+ };
639
+ await db.updateSettings({ orgEmailConfig } as any);
640
+ return c.json({ success: true, orgEmailConfig: { configured: true, provider, label, oauthClientId, oauthTenantId: orgEmailConfig.oauthTenantId } });
641
+ });
642
+
643
+ api.delete('/settings/org-email', requireRole('admin'), async (c) => {
644
+ await db.updateSettings({ orgEmailConfig: null } as any);
645
+ return c.json({ success: true });
646
+ });
647
+
605
648
  // ─── Tool Security Config ─────────────────────────
606
649
 
607
650
  api.get('/settings/tool-security', requireRole('admin'), async (c) => {
@@ -3925,18 +3925,22 @@ function EmailSection(props) {
3925
3925
  });
3926
3926
  } else if (form.provider === 'microsoft') {
3927
3927
  var baseUrl = window.location.origin;
3928
+ var hasOrgMs = emailConfig && emailConfig.orgEmailConfig && emailConfig.orgEmailConfig.provider === 'microsoft';
3928
3929
  Object.assign(body, {
3929
- oauthClientId: form.oauthClientId,
3930
- oauthClientSecret: form.oauthClientSecret,
3930
+ oauthClientId: form.oauthClientId || undefined,
3931
+ oauthClientSecret: form.oauthClientSecret || undefined,
3931
3932
  oauthTenantId: form.oauthTenantId,
3932
3933
  oauthRedirectUri: baseUrl + '/api/engine/oauth/callback',
3934
+ useOrgConfig: hasOrgMs && !form.oauthClientId ? true : undefined,
3933
3935
  });
3934
3936
  } else if (form.provider === 'google') {
3935
3937
  var gBaseUrl = window.location.origin;
3938
+ var hasOrgG = emailConfig && emailConfig.orgEmailConfig && emailConfig.orgEmailConfig.provider === 'google';
3936
3939
  Object.assign(body, {
3937
- oauthClientId: form.oauthClientId,
3938
- oauthClientSecret: form.oauthClientSecret,
3940
+ oauthClientId: form.oauthClientId || undefined,
3941
+ oauthClientSecret: form.oauthClientSecret || undefined,
3939
3942
  oauthRedirectUri: gBaseUrl + '/api/engine/oauth/callback',
3943
+ useOrgConfig: hasOrgG && !form.oauthClientId ? true : undefined,
3940
3944
  });
3941
3945
  }
3942
3946
 
@@ -4023,6 +4027,15 @@ function EmailSection(props) {
4023
4027
  ),
4024
4028
  h('div', { className: 'card-body' },
4025
4029
 
4030
+ // ─── Org Email Config Banner ──────────────────────
4031
+ emailConfig && emailConfig.orgEmailConfig && h('div', { style: { padding: '12px 16px', background: 'var(--success-soft)', borderRadius: 'var(--radius)', marginBottom: 16, display: 'flex', alignItems: 'center', gap: 10 } },
4032
+ h('span', { style: { fontSize: 18 } }, '\u2705'),
4033
+ h('div', null,
4034
+ h('div', { style: { fontSize: 13, fontWeight: 600 } }, 'Your organization has configured ', emailConfig.orgEmailConfig.label || emailConfig.orgEmailConfig.provider),
4035
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)' } }, 'Select ', emailConfig.orgEmailConfig.provider === 'google' ? 'Google OAuth' : 'Microsoft OAuth', ' below — Client ID and Secret will be inherited automatically.')
4036
+ )
4037
+ ),
4038
+
4026
4039
  // ─── Provider Selection ─────────────────────────
4027
4040
  h('div', { style: { marginBottom: 20 } },
4028
4041
  h('label', { style: labelStyle }, 'Connection Method'),
@@ -4145,16 +4158,24 @@ function EmailSection(props) {
4145
4158
  ),
4146
4159
 
4147
4160
  // ─── Google OAuth Config ────────────────────────
4148
- form.provider === 'google' && h(Fragment, null,
4149
- h('div', { style: { padding: '12px 16px', background: 'var(--info-soft)', borderRadius: 'var(--radius)', fontSize: 12, color: 'var(--info)', marginBottom: 16 } },
4150
- h('strong', null, 'Setup Instructions:'), h('br'),
4151
- '1. Go to ', h('a', { href: 'https://console.cloud.google.com/apis/credentials', target: '_blank', style: { color: 'var(--accent)' } }, 'Google Cloud Console → Credentials'), h('br'),
4152
- '2. Create an OAuth 2.0 Client ID (Web application) → add redirect URI: ', h('code', { style: { background: 'var(--bg-tertiary)', padding: '1px 4px', borderRadius: 3 } }, window.location.origin + '/api/engine/oauth/callback'), h('br'),
4153
- '3. Enable the Gmail API in your project', h('br'),
4154
- '4. Copy the Client ID and Client Secret below'
4155
- ),
4161
+ form.provider === 'google' && (function() {
4162
+ var hasOrg = emailConfig && emailConfig.orgEmailConfig && emailConfig.orgEmailConfig.provider === 'google';
4163
+ return h(Fragment, null,
4164
+ hasOrg
4165
+ ? h('div', { style: { padding: '12px 16px', background: 'var(--success-soft)', borderRadius: 'var(--radius)', fontSize: 12, marginBottom: 16 } },
4166
+ h('strong', null, '\u2705 Using organization Google Workspace credentials'), h('br'),
4167
+ 'Client ID: ', h('code', { style: { fontSize: 11 } }, emailConfig.orgEmailConfig.oauthClientId), h('br'),
4168
+ h('span', { style: { color: 'var(--text-muted)' } }, 'Just click "Save Configuration" then authorize with the agent\'s Google account.')
4169
+ )
4170
+ : h('div', { style: { padding: '12px 16px', background: 'var(--info-soft)', borderRadius: 'var(--radius)', fontSize: 12, color: 'var(--info)', marginBottom: 16 } },
4171
+ h('strong', null, 'Setup Instructions:'), h('br'),
4172
+ '1. Go to ', h('a', { href: 'https://console.cloud.google.com/apis/credentials', target: '_blank', style: { color: 'var(--accent)' } }, 'Google Cloud Console \u2192 Credentials'), h('br'),
4173
+ '2. Create an OAuth 2.0 Client ID (Web application) \u2192 add redirect URI: ', h('code', { style: { background: 'var(--bg-tertiary)', padding: '1px 4px', borderRadius: 3 } }, window.location.origin + '/api/engine/oauth/callback'), h('br'),
4174
+ '3. Enable the Gmail API in your project', h('br'),
4175
+ '4. Copy the Client ID and Client Secret below'
4176
+ ),
4156
4177
 
4157
- h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 } },
4178
+ !hasOrg && h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 } },
4158
4179
  h('div', null,
4159
4180
  h('label', { style: labelStyle }, 'OAuth Client ID *'),
4160
4181
  h('input', { style: inputStyle, value: form.oauthClientId, placeholder: 'xxxx.apps.googleusercontent.com', onChange: function(e) { set('oauthClientId', e.target.value); } })
@@ -4170,7 +4191,7 @@ function EmailSection(props) {
4170
4191
  h('p', { style: { fontSize: 12, margin: '0 0 8px', color: 'var(--text-secondary)' } }, 'Click the button below to sign in with the agent\'s Google account and grant Gmail permissions.'),
4171
4192
  h('button', { className: 'btn btn-primary btn-sm', onClick: openOAuth }, 'Authorize with Google')
4172
4193
  )
4173
- ),
4194
+ ); })(),
4174
4195
 
4175
4196
  // ─── Test Result ─────────────────────────────────
4176
4197
  testResult && h('div', { style: { padding: '12px 16px', borderRadius: 'var(--radius)', marginBottom: 16, background: testResult.success ? 'var(--success-soft)' : 'var(--danger-soft)' } },
@@ -53,6 +53,12 @@ export function SettingsPage() {
53
53
  var _apiKeyInput = useState('');
54
54
  var apiKeyInput = _apiKeyInput[0]; var setApiKeyInput = _apiKeyInput[1];
55
55
 
56
+ // Org Email Config
57
+ var _orgEmail = useState({ configured: false, provider: '', oauthClientId: '', oauthClientSecret: '', oauthTenantId: 'common', label: '' });
58
+ var orgEmail = _orgEmail[0]; var setOrgEmail = _orgEmail[1];
59
+ var _orgEmailSaving = useState(false);
60
+ var orgEmailSaving = _orgEmailSaving[0]; var setOrgEmailSaving = _orgEmailSaving[1];
61
+
56
62
  useEffect(() => {
57
63
  apiCall('/settings').then(d => { const s = d.settings || d || {}; setSettings(s); if (s.primaryColor) applyBrandColor(s.primaryColor); if (s.orgId) setOrgId(s.orgId); }).catch(() => {});
58
64
  apiCall('/api-keys').then(d => setApiKeys(d.keys || [])).catch(() => {});
@@ -66,6 +72,9 @@ export function SettingsPage() {
66
72
  }));
67
73
  }).catch(() => {});
68
74
  engineCall('/deploy-credentials?orgId=' + getOrgId()).then(d => setDeployCreds(d.credentials || [])).catch(() => {});
75
+ apiCall('/settings/org-email').then(d => {
76
+ if (d.configured) setOrgEmail({ configured: true, provider: d.provider, oauthClientId: d.oauthClientId || '', oauthClientSecret: '', oauthTenantId: d.oauthTenantId || 'common', label: d.label || '' });
77
+ }).catch(() => {});
69
78
  apiCall('/settings/tool-security').then(d => {
70
79
  var cfg = d.toolSecurityConfig || {};
71
80
  setToolSec({
@@ -108,6 +117,29 @@ export function SettingsPage() {
108
117
  try { await apiCall('/settings', { method: 'PATCH', body: JSON.stringify({ [key]: value }) }); toast('Settings saved', 'success'); } catch (e) { toast(e.message, 'error'); }
109
118
  };
110
119
 
120
+ const saveOrgEmail = async () => {
121
+ if (!orgEmail.provider) { toast('Select a provider', 'error'); return; }
122
+ if (!orgEmail.oauthClientId) { toast('Enter Client ID', 'error'); return; }
123
+ if (!orgEmail.oauthClientSecret) { toast('Enter Client Secret', 'error'); return; }
124
+ setOrgEmailSaving(true);
125
+ try {
126
+ var result = await apiCall('/settings/org-email', { method: 'PUT', body: JSON.stringify({ provider: orgEmail.provider, oauthClientId: orgEmail.oauthClientId, oauthClientSecret: orgEmail.oauthClientSecret, oauthTenantId: orgEmail.oauthTenantId }) });
127
+ setOrgEmail(function(prev) { return Object.assign({}, prev, { configured: true, label: result.orgEmailConfig?.label || prev.label, oauthClientSecret: '' }); });
128
+ toast('Organization email configuration saved', 'success');
129
+ } catch (e) { toast(e.message, 'error'); }
130
+ setOrgEmailSaving(false);
131
+ };
132
+
133
+ const removeOrgEmail = async () => {
134
+ var ok = await showConfirm({ title: 'Remove Organization Email', message: 'Agents using this org-level config will need to be individually configured.', danger: true, confirmText: 'Remove' });
135
+ if (!ok) return;
136
+ try {
137
+ await apiCall('/settings/org-email', { method: 'DELETE' });
138
+ setOrgEmail({ configured: false, provider: '', oauthClientId: '', oauthClientSecret: '', oauthTenantId: 'common', label: '' });
139
+ toast('Organization email configuration removed', 'success');
140
+ } catch (e) { toast(e.message, 'error'); }
141
+ };
142
+
111
143
  const saveSaml = async () => {
112
144
  try {
113
145
  await apiCall('/settings/sso/saml', { method: 'PUT', body: JSON.stringify({ entityId: settings.samlEntityId, ssoUrl: settings.samlSsoUrl, certificate: settings.samlCertificate }) });
@@ -225,14 +257,76 @@ export function SettingsPage() {
225
257
  )
226
258
  ),
227
259
  h('div', { className: 'card' },
228
- h('div', { className: 'card-header' }, h('h3', null, 'Email Configuration')),
260
+ h('div', { className: 'card-header' },
261
+ h('h3', null, 'Organization Email'),
262
+ orgEmail.configured && h('span', { className: 'badge badge-success', style: { marginLeft: 8 } }, orgEmail.label || 'Configured')
263
+ ),
229
264
  h('div', { className: 'card-body' },
230
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)' } },
231
- h('div', { style: { fontSize: 24 } }, '\u2709\uFE0F'),
232
- h('div', null,
233
- h('p', { style: { fontSize: 14, fontWeight: 600, marginBottom: 4 } }, 'Email is configured per agent'),
234
- h('p', { style: { fontSize: 13, color: 'var(--text-muted)' } }, 'Each agent has its own email credentials (IMAP/SMTP, Microsoft 365 OAuth, or Google OAuth). Configure email from each agent\'s detail page under the Email tab.')
265
+ h('p', { style: { fontSize: 13, color: 'var(--text-muted)', marginBottom: 16 } }, 'Set up a shared OAuth application for all agents. Each agent will still authorize individually with their own account, but they\'ll use the same Client ID and Secret.'),
266
+
267
+ // Provider selector
268
+ h('div', { style: { display: 'flex', gap: 12, marginBottom: 16 } },
269
+ h('div', {
270
+ onClick: function() { setOrgEmail(function(p) { return Object.assign({}, p, { provider: 'google' }); }); },
271
+ style: { flex: 1, padding: '16px 12px', border: '2px solid ' + (orgEmail.provider === 'google' ? 'var(--accent)' : 'var(--border)'), borderRadius: 'var(--radius)', cursor: 'pointer', textAlign: 'center', background: orgEmail.provider === 'google' ? 'var(--accent-soft)' : 'var(--bg-primary)' }
272
+ },
273
+ h('div', { style: { fontSize: 20, marginBottom: 4 } }, '\uD83D\uDD35'),
274
+ h('div', { style: { fontSize: 13, fontWeight: 600 } }, 'Google Workspace'),
275
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Gmail API OAuth')
276
+ ),
277
+ h('div', {
278
+ onClick: function() { setOrgEmail(function(p) { return Object.assign({}, p, { provider: 'microsoft' }); }); },
279
+ style: { flex: 1, padding: '16px 12px', border: '2px solid ' + (orgEmail.provider === 'microsoft' ? 'var(--accent)' : 'var(--border)'), borderRadius: 'var(--radius)', cursor: 'pointer', textAlign: 'center', background: orgEmail.provider === 'microsoft' ? 'var(--accent-soft)' : 'var(--bg-primary)' }
280
+ },
281
+ h('div', { style: { fontSize: 20, marginBottom: 4 } }, '\uD83C\uDFE2'),
282
+ h('div', { style: { fontSize: 13, fontWeight: 600 } }, 'Microsoft 365'),
283
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Azure AD / Entra ID')
284
+ )
285
+ ),
286
+
287
+ // Setup instructions
288
+ orgEmail.provider && h('div', { style: { padding: '12px 16px', background: 'var(--info-soft)', borderRadius: 'var(--radius)', marginBottom: 16, fontSize: 12, color: 'var(--text-secondary)' } },
289
+ h('strong', { style: { color: 'var(--accent)' } }, 'Setup Instructions:'), h('br'),
290
+ orgEmail.provider === 'google'
291
+ ? h(Fragment, null,
292
+ '1. Go to ', h('a', { href: 'https://console.cloud.google.com/apis/credentials', target: '_blank', style: { color: 'var(--accent)' } }, 'Google Cloud Console \u2192 Credentials'), h('br'),
293
+ '2. Create an OAuth 2.0 Client ID (Web application) \u2192 add redirect URI: ', h('code', { style: { background: 'var(--bg-tertiary)', padding: '1px 4px', borderRadius: 3, fontSize: 11 } }, window.location.origin + '/api/engine/oauth/callback'), h('br'),
294
+ '3. Enable the Gmail API in your project', h('br'),
295
+ '4. Copy the Client ID and Client Secret below'
296
+ )
297
+ : h(Fragment, null,
298
+ '1. Go to ', h('a', { href: 'https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade', target: '_blank', style: { color: 'var(--accent)' } }, 'Azure Portal \u2192 App Registrations'), h('br'),
299
+ '2. Click "New Registration" \u2192 set redirect URI to: ', h('code', { style: { background: 'var(--bg-tertiary)', padding: '1px 4px', borderRadius: 3, fontSize: 11 } }, window.location.origin + '/api/engine/oauth/callback'), h('br'),
300
+ '3. Copy the Client ID and create a Client Secret below'
301
+ )
302
+ ),
303
+
304
+ // Credentials form
305
+ orgEmail.provider && h(Fragment, null,
306
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 } },
307
+ h('div', null,
308
+ h('label', { className: 'form-label' }, 'OAuth Client ID *'),
309
+ h('input', { className: 'input', value: orgEmail.oauthClientId, placeholder: orgEmail.provider === 'google' ? 'xxxx.apps.googleusercontent.com' : 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', onChange: function(e) { setOrgEmail(function(p) { return Object.assign({}, p, { oauthClientId: e.target.value }); }); } })
310
+ ),
311
+ h('div', null,
312
+ h('label', { className: 'form-label' }, 'Client Secret *'),
313
+ h('input', { className: 'input', type: 'password', value: orgEmail.oauthClientSecret, placeholder: orgEmail.configured ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022 (saved)' : 'Enter client secret', onChange: function(e) { setOrgEmail(function(p) { return Object.assign({}, p, { oauthClientSecret: e.target.value }); }); } })
314
+ )
315
+ ),
316
+ orgEmail.provider === 'microsoft' && h('div', { style: { marginBottom: 12 } },
317
+ h('label', { className: 'form-label' }, 'Tenant ID'),
318
+ h('input', { className: 'input', style: { maxWidth: 400 }, value: orgEmail.oauthTenantId, placeholder: 'common', onChange: function(e) { setOrgEmail(function(p) { return Object.assign({}, p, { oauthTenantId: e.target.value }); }); } }),
319
+ h('p', { className: 'form-help' }, 'Use "common" for multi-tenant or your specific tenant ID')
320
+ ),
321
+ h('div', { style: { display: 'flex', gap: 8 } },
322
+ h('button', { className: 'btn btn-primary', disabled: orgEmailSaving, onClick: saveOrgEmail }, orgEmailSaving ? 'Saving...' : (orgEmail.configured ? 'Update Configuration' : 'Save Configuration')),
323
+ orgEmail.configured && h('button', { className: 'btn btn-danger', onClick: removeOrgEmail }, 'Remove')
235
324
  )
325
+ ),
326
+
327
+ // Info about per-agent auth
328
+ orgEmail.configured && h('div', { style: { marginTop: 16, padding: '12px 16px', background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)', fontSize: 12, color: 'var(--text-muted)' } },
329
+ '\u2139\uFE0F Each agent still needs to individually authorize via their Email tab. This org config provides the shared OAuth app credentials so agents don\'t need to enter Client ID/Secret individually.'
236
330
  )
237
331
  )
238
332
  ),
package/src/db/adapter.ts CHANGED
@@ -200,10 +200,22 @@ export interface CompanySettings {
200
200
  toolSecurityConfig?: Record<string, any>;
201
201
  firewallConfig?: FirewallConfig;
202
202
  modelPricingConfig?: ModelPricingConfig;
203
+ orgEmailConfig?: OrgEmailConfig;
203
204
  createdAt: Date;
204
205
  updatedAt: Date;
205
206
  }
206
207
 
208
+ export interface OrgEmailConfig {
209
+ provider: 'google' | 'microsoft';
210
+ oauthClientId: string;
211
+ oauthClientSecret: string;
212
+ oauthTenantId?: string; // Microsoft only
213
+ oauthRedirectUri?: string;
214
+ oauthScopes?: string[];
215
+ configured: boolean;
216
+ label?: string; // e.g. "Google Workspace" or "Microsoft 365"
217
+ }
218
+
207
219
  export interface FirewallConfig {
208
220
  ipAccess?: {
209
221
  enabled?: boolean;
@@ -106,6 +106,9 @@ export class PostgresAdapter extends DatabaseAdapter {
106
106
  ALTER TABLE company_settings ADD COLUMN IF NOT EXISTS cf_api_token TEXT;
107
107
  ALTER TABLE company_settings ADD COLUMN IF NOT EXISTS cf_account_id TEXT;
108
108
  `).catch(() => {});
109
+ await client.query(`
110
+ ALTER TABLE company_settings ADD COLUMN IF NOT EXISTS org_email_config JSONB;
111
+ `).catch(() => {});
109
112
  await client.query('COMMIT');
110
113
  } catch (err) {
111
114
  await client.query('ROLLBACK');
@@ -175,6 +178,11 @@ export class PostgresAdapter extends DatabaseAdapter {
175
178
  values.push(JSON.stringify(updates.modelPricingConfig));
176
179
  i++;
177
180
  }
181
+ if (updates.orgEmailConfig !== undefined) {
182
+ fields.push(`org_email_config = $${i}`);
183
+ values.push(JSON.stringify(updates.orgEmailConfig));
184
+ i++;
185
+ }
178
186
  fields.push(`updated_at = NOW()`);
179
187
  values.push('default');
180
188
  const { rows } = await this.pool.query(
@@ -553,6 +561,7 @@ export class PostgresAdapter extends DatabaseAdapter {
553
561
  domainStatus: r.domain_status || 'unregistered',
554
562
  cfApiToken: r.cf_api_token || undefined,
555
563
  cfAccountId: r.cf_account_id || undefined,
564
+ orgEmailConfig: r.org_email_config ? (typeof r.org_email_config === 'string' ? JSON.parse(r.org_email_config) : r.org_email_config) : undefined,
556
565
  };
557
566
  }
558
567
  }
@@ -410,13 +410,30 @@ export function createAgentRoutes(opts: {
410
410
  /**
411
411
  * GET /bridge/agents/:id/email-config — Get agent's email configuration (without password).
412
412
  */
413
- router.get('/bridge/agents/:id/email-config', (c) => {
413
+ router.get('/bridge/agents/:id/email-config', async (c) => {
414
414
  const agentId = c.req.param('id');
415
415
  const managed = lifecycle.getAgent(agentId);
416
416
  if (!managed) return c.json({ error: 'Agent not found' }, 404);
417
417
 
418
+ // Fetch org-level email config
419
+ let orgEmailConfig: any = null;
420
+ try {
421
+ const adminDb = getAdminDb();
422
+ if (adminDb) {
423
+ const settings = await adminDb.getSettings();
424
+ if (settings?.orgEmailConfig?.configured) {
425
+ orgEmailConfig = {
426
+ provider: settings.orgEmailConfig.provider,
427
+ label: settings.orgEmailConfig.label,
428
+ oauthClientId: settings.orgEmailConfig.oauthClientId,
429
+ oauthTenantId: settings.orgEmailConfig.oauthTenantId,
430
+ };
431
+ }
432
+ }
433
+ } catch {}
434
+
418
435
  const emailConfig = managed.config?.emailConfig || null;
419
- if (!emailConfig) return c.json({ configured: false });
436
+ if (!emailConfig) return c.json({ configured: false, orgEmailConfig });
420
437
 
421
438
  // Return config without sensitive data
422
439
  return c.json({
@@ -436,6 +453,7 @@ export function createAgentRoutes(opts: {
436
453
  oauthAuthUrl: emailConfig.oauthAuthUrl || undefined,
437
454
  lastConnected: emailConfig.lastConnected,
438
455
  lastError: emailConfig.lastError,
456
+ orgEmailConfig,
439
457
  });
440
458
  });
441
459
 
@@ -455,10 +473,28 @@ export function createAgentRoutes(opts: {
455
473
  if (!managed) return c.json({ error: 'Agent not found' }, 404);
456
474
 
457
475
  const body = await c.req.json();
458
- const { provider, email, password, imapHost, imapPort, smtpHost, smtpPort, preset,
476
+ let { provider, email, password, imapHost, imapPort, smtpHost, smtpPort, preset,
459
477
  oauthClientId, oauthClientSecret, oauthTenantId, oauthRedirectUri } = body;
478
+ const useOrgConfig = body.useOrgConfig === true;
460
479
 
461
480
  if (!provider) return c.json({ error: 'provider is required (imap, microsoft, or google)' }, 400);
481
+
482
+ // If using org-level OAuth config, inherit client credentials
483
+ if (useOrgConfig && (provider === 'google' || provider === 'microsoft')) {
484
+ try {
485
+ const adminDb = getAdminDb();
486
+ if (adminDb) {
487
+ const settings = await adminDb.getSettings();
488
+ if (settings?.orgEmailConfig?.configured && settings.orgEmailConfig.provider === provider) {
489
+ oauthClientId = oauthClientId || settings.orgEmailConfig.oauthClientId;
490
+ oauthClientSecret = oauthClientSecret || settings.orgEmailConfig.oauthClientSecret;
491
+ if (provider === 'microsoft') oauthTenantId = oauthTenantId || settings.orgEmailConfig.oauthTenantId;
492
+ } else {
493
+ return c.json({ error: 'Organization email config not found or provider mismatch' }, 400);
494
+ }
495
+ }
496
+ } catch {}
497
+ }
462
498
  if (!email && provider === 'imap') return c.json({ error: 'email is required' }, 400);
463
499
 
464
500
  const emailConfig: any = {