@agenticmail/enterprise 0.5.301 → 0.5.303

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.
Files changed (52) hide show
  1. package/dist/chunk-4VGAZULN.js +1519 -0
  2. package/dist/chunk-CRXYUYVJ.js +4395 -0
  3. package/dist/chunk-QCGSFWKU.js +48 -0
  4. package/dist/cli-agent-YSQVDBOZ.js +1778 -0
  5. package/dist/cli-recover-2LWWVD4Q.js +487 -0
  6. package/dist/cli-serve-JZEXPYXY.js +143 -0
  7. package/dist/cli-verify-TZMX3GWV.js +149 -0
  8. package/dist/cli.js +5 -5
  9. package/dist/dashboard/app.js +57 -1
  10. package/dist/dashboard/components/org-switcher.js +118 -0
  11. package/dist/dashboard/pages/agents.js +8 -1
  12. package/dist/dashboard/pages/approvals.js +5 -1
  13. package/dist/dashboard/pages/dashboard.js +6 -2
  14. package/dist/dashboard/pages/guardrails.js +20 -16
  15. package/dist/dashboard/pages/journal.js +6 -2
  16. package/dist/dashboard/pages/knowledge-contributions.js +18 -10
  17. package/dist/dashboard/pages/knowledge.js +32 -9
  18. package/dist/dashboard/pages/messages.js +8 -4
  19. package/dist/dashboard/pages/org-chart.js +5 -1
  20. package/dist/dashboard/pages/organizations.js +39 -3
  21. package/dist/dashboard/pages/skills.js +15 -11
  22. package/dist/dashboard/pages/task-pipeline.js +6 -2
  23. package/dist/dashboard/pages/users.js +34 -4
  24. package/dist/dashboard/pages/workforce.js +5 -1
  25. package/dist/factory-QYGGXVYW.js +9 -0
  26. package/dist/index.js +3 -3
  27. package/dist/postgres-ALNOGUUM.js +819 -0
  28. package/dist/server-3R5KZPLA.js +15 -0
  29. package/dist/setup-ZZAOBEY4.js +20 -0
  30. package/dist/sqlite-2QPVZQ27.js +566 -0
  31. package/package.json +1 -1
  32. package/src/admin/routes.ts +32 -1
  33. package/src/auth/routes.ts +36 -2
  34. package/src/dashboard/app.js +57 -1
  35. package/src/dashboard/components/org-switcher.js +118 -0
  36. package/src/dashboard/pages/agents.js +8 -1
  37. package/src/dashboard/pages/approvals.js +5 -1
  38. package/src/dashboard/pages/dashboard.js +6 -2
  39. package/src/dashboard/pages/guardrails.js +20 -16
  40. package/src/dashboard/pages/journal.js +6 -2
  41. package/src/dashboard/pages/knowledge-contributions.js +18 -10
  42. package/src/dashboard/pages/knowledge.js +32 -9
  43. package/src/dashboard/pages/messages.js +8 -4
  44. package/src/dashboard/pages/org-chart.js +5 -1
  45. package/src/dashboard/pages/organizations.js +39 -3
  46. package/src/dashboard/pages/skills.js +15 -11
  47. package/src/dashboard/pages/task-pipeline.js +6 -2
  48. package/src/dashboard/pages/users.js +34 -4
  49. package/src/dashboard/pages/workforce.js +5 -1
  50. package/src/db/adapter.ts +1 -0
  51. package/src/db/postgres.ts +3 -0
  52. package/src/db/sqlite.ts +7 -0
@@ -303,7 +303,7 @@ export function createAuthRoutes(
303
303
  token,
304
304
  refreshToken,
305
305
  csrf,
306
- user: { id: user.id, email: user.email, name: user.name, role: user.role, totpEnabled: !!user.totpEnabled },
306
+ user: { id: user.id, email: user.email, name: user.name, role: user.role, totpEnabled: !!user.totpEnabled, clientOrgId: user.clientOrgId || null },
307
307
  mustResetPassword: !!user.mustResetPassword,
308
308
  });
309
309
  });
@@ -362,7 +362,7 @@ export function createAuthRoutes(
362
362
  token,
363
363
  refreshToken,
364
364
  csrf,
365
- user: { id: user.id, email: user.email, name: user.name, role: user.role, totpEnabled: true },
365
+ user: { id: user.id, email: user.email, name: user.name, role: user.role, totpEnabled: true, clientOrgId: user.clientOrgId || null },
366
366
  mustResetPassword: !!user.mustResetPassword,
367
367
  ...(backupUsed ? { warning: 'Backup code used. You have fewer backup codes remaining.' } : {}),
368
368
  });
@@ -672,6 +672,40 @@ export function createAuthRoutes(
672
672
  }
673
673
  });
674
674
 
675
+ // ─── Impersonation (owner-only) ──────────────────────────
676
+
677
+ auth.post('/impersonate/:userId', async (c) => {
678
+ // Only owners can impersonate
679
+ const token = await extractToken(c);
680
+ if (!token) return c.json({ error: 'Authentication required' }, 401);
681
+ try {
682
+ const { jwtVerify, SignJWT } = await import('jose');
683
+ const secret = new TextEncoder().encode(jwtSecret);
684
+ const { payload } = await jwtVerify(token, secret);
685
+ const caller = await db.getUser(payload.sub as string);
686
+ if (!caller || caller.role !== 'owner') return c.json({ error: 'Only owners can impersonate users' }, 403);
687
+
688
+ const targetId = c.req.param('userId');
689
+ const target = await db.getUser(targetId);
690
+ if (!target) return c.json({ error: 'User not found' }, 404);
691
+
692
+ // Generate a short-lived token (1 hour) for the target user with impersonation flag
693
+ const impersonateToken = await new SignJWT({ sub: target.id, role: target.role, impersonatedBy: caller.id })
694
+ .setProtectedHeader({ alg: 'HS256' })
695
+ .setIssuedAt()
696
+ .setExpirationTime('1h')
697
+ .sign(secret);
698
+
699
+ return c.json({
700
+ token: impersonateToken,
701
+ user: { id: target.id, email: target.email, name: target.name, role: target.role, totpEnabled: !!target.totpEnabled, clientOrgId: target.clientOrgId || null, permissions: target.permissions },
702
+ impersonatedBy: { id: caller.id, name: caller.name, email: caller.email },
703
+ });
704
+ } catch (e: any) {
705
+ return c.json({ error: e.message || 'Impersonation failed' }, 500);
706
+ }
707
+ });
708
+
675
709
  // ─── Logout ─────────────────────────────────────────────
676
710
 
677
711
  auth.post('/logout', (c) => {
@@ -97,6 +97,7 @@ function App() {
97
97
  const [permissions, setPermissions] = useState('*'); // '*' = full access, or { pageId: true | ['tab1','tab2'] }
98
98
  const [mustResetPassword, setMustResetPassword] = useState(false);
99
99
  const [show2faReminder, setShow2faReminder] = useState(false);
100
+ const [impersonating, setImpersonating] = useState(null); // { user, impersonatedBy }
100
101
  const [forceResetPw, setForceResetPw] = useState('');
101
102
  const [forceResetPw2, setForceResetPw2] = useState('');
102
103
  const [forceResetLoading, setForceResetLoading] = useState(false);
@@ -146,6 +147,12 @@ function App() {
146
147
  apiCall('/settings').then(d => { const s = d.settings || d || {}; if (s.primaryColor) applyBrandColor(s.primaryColor); if (s.orgId) setOrgId(s.orgId); }).catch(() => {});
147
148
  apiCall('/me/permissions').then(d => {
148
149
  if (d && d.permissions) setPermissions(d.permissions);
150
+ // If user is assigned to a client org, auto-set org context
151
+ if (d && d.clientOrgId) {
152
+ localStorage.setItem('em_client_org_id', d.clientOrgId);
153
+ } else {
154
+ localStorage.removeItem('em_client_org_id');
155
+ }
149
156
  }).catch(() => {});
150
157
  }, [authed]);
151
158
 
@@ -281,7 +288,44 @@ function App() {
281
288
  const PageComponent = canAccessPage ? (pages[page] || DashboardPage) : null;
282
289
  const sidebarClass = 'sidebar' + (sidebarPinned ? ' expanded' : sidebarHovered ? ' hover-expanded' : '') + (mobileMenuOpen ? ' mobile-open' : '');
283
290
 
284
- return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions } },
291
+ // Impersonation functions
292
+ const startImpersonation = useCallback(async (userId) => {
293
+ try {
294
+ const d = await authCall('/impersonate/' + userId, { method: 'POST' });
295
+ if (d.token && d.user) {
296
+ // Store real user info
297
+ setImpersonating({ user: d.user, impersonatedBy: d.impersonatedBy, originalToken: localStorage.getItem('em_token') });
298
+ // Set impersonated user's token
299
+ localStorage.setItem('em_token', d.token);
300
+ setUser(d.user);
301
+ if (d.user.permissions) setPermissions(d.user.permissions);
302
+ if (d.user.clientOrgId) {
303
+ localStorage.setItem('em_client_org_id', d.user.clientOrgId);
304
+ // Fetch org name for display
305
+ apiCall('/organizations/' + d.user.clientOrgId).then(function(o) {
306
+ if (o && o.name) setImpersonating(function(prev) { return prev ? Object.assign({}, prev, { user: Object.assign({}, prev.user, { clientOrgName: o.name }) }) : prev; });
307
+ }).catch(function() {});
308
+ } else localStorage.removeItem('em_client_org_id');
309
+ toast('Now viewing as ' + d.user.name, 'info');
310
+ setPage('dashboard');
311
+ }
312
+ } catch (e) { toast(e.message || 'Impersonation failed', 'error'); }
313
+ }, []);
314
+
315
+ const stopImpersonation = useCallback(() => {
316
+ if (impersonating && impersonating.originalToken) {
317
+ localStorage.setItem('em_token', impersonating.originalToken);
318
+ }
319
+ setImpersonating(null);
320
+ localStorage.removeItem('em_client_org_id');
321
+ // Reload real user
322
+ authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {});
323
+ apiCall('/me/permissions').then(d => { if (d && d.permissions) setPermissions(d.permissions); }).catch(() => {});
324
+ toast('Stopped impersonation', 'success');
325
+ setPage('users');
326
+ }, [impersonating]);
327
+
328
+ return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions, impersonating, startImpersonation, stopImpersonation } },
285
329
  h('div', { className: 'app-layout' },
286
330
  // Mobile hamburger
287
331
  h('button', { className: 'mobile-hamburger', onClick: () => setMobileMenuOpen(true) },
@@ -337,6 +381,18 @@ function App() {
337
381
  )
338
382
  ),
339
383
  h('div', { className: 'page-content' },
384
+ // Impersonation banner
385
+ impersonating && h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', margin: '0 0 16px', background: 'rgba(99,102,241,0.12)', border: '2px solid var(--primary, #6366f1)', borderRadius: 8, fontSize: 13 } },
386
+ I.agents(),
387
+ h('div', { style: { flex: 1 } },
388
+ h('strong', null, 'Viewing as: '),
389
+ impersonating.user.name + ' (' + impersonating.user.email + ')',
390
+ impersonating.user.role && h('span', { className: 'badge badge-neutral', style: { marginLeft: 8, fontSize: 10 } }, impersonating.user.role),
391
+ impersonating.user.clientOrgName && h('span', { className: 'badge badge-info', style: { marginLeft: 8, fontSize: 10 } }, 'Org: ' + impersonating.user.clientOrgName),
392
+ impersonating.user.clientOrgId && !impersonating.user.clientOrgName && h('span', { className: 'badge badge-info', style: { marginLeft: 8, fontSize: 10 } }, 'Client Org')
393
+ ),
394
+ h('button', { className: 'btn btn-primary btn-sm', onClick: stopImpersonation }, 'Stop Impersonating')
395
+ ),
340
396
  // 2FA recommendation banner
341
397
  show2faReminder && h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', margin: '0 0 16px', background: 'var(--warning-soft, rgba(245,158,11,0.1))', border: '1px solid var(--warning, #f59e0b)', borderRadius: 8, fontSize: 13 } },
342
398
  I.shield(),
@@ -0,0 +1,118 @@
1
+ import { h, useState, useEffect, Fragment, apiCall, useApp } from './utils.js';
2
+ import { I } from './icons.js';
3
+
4
+ /**
5
+ * OrgContextSwitcher — Global org context picker for multi-tenant pages.
6
+ *
7
+ * If the current user has a clientOrgId, the switcher is LOCKED to that org
8
+ * (they can only see their org's data). Owners/admins can switch freely.
9
+ */
10
+ export function OrgContextSwitcher(props) {
11
+ var onOrgChange = props.onOrgChange;
12
+ var selectedOrgId = props.selectedOrgId || '';
13
+ var showLabel = props.showLabel !== false;
14
+ var style = props.style || {};
15
+
16
+ var app = useApp();
17
+ var user = app.user || {};
18
+ var userOrgId = user.clientOrgId || null;
19
+ var isLocked = !!userOrgId && user.role !== 'owner' && user.role !== 'admin';
20
+
21
+ var _orgs = useState([]);
22
+ var orgs = _orgs[0]; var setOrgs = _orgs[1];
23
+ var _loaded = useState(false);
24
+ var loaded = _loaded[0]; var setLoaded = _loaded[1];
25
+
26
+ useEffect(function() {
27
+ apiCall('/organizations').then(function(d) {
28
+ var list = d.organizations || [];
29
+ setOrgs(list);
30
+ setLoaded(true);
31
+ // Auto-select user's org on first load if org-bound
32
+ if (userOrgId && !selectedOrgId) {
33
+ var org = list.find(function(o) { return o.id === userOrgId; });
34
+ if (org) onOrgChange(userOrgId, org);
35
+ }
36
+ }).catch(function() { setLoaded(true); });
37
+ }, [userOrgId]);
38
+
39
+ // Don't render if no client orgs and user isn't org-bound
40
+ if (loaded && orgs.length === 0 && !userOrgId) return null;
41
+ if (!loaded) return null;
42
+
43
+ var effectiveId = isLocked ? userOrgId : selectedOrgId;
44
+ var selectedOrg = orgs.find(function(o) { return o.id === effectiveId; });
45
+
46
+ return h('div', {
47
+ style: Object.assign({
48
+ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 14px',
49
+ background: 'var(--bg-tertiary)', borderRadius: 'var(--radius, 8px)',
50
+ marginBottom: 16, fontSize: 13
51
+ }, style)
52
+ },
53
+ showLabel && h('span', { style: { color: 'var(--text-muted)', fontWeight: 600, whiteSpace: 'nowrap' } }, I.building(), ' Viewing:'),
54
+ isLocked
55
+ ? h('div', { style: { fontWeight: 600, fontSize: 13, color: 'var(--text)', display: 'flex', alignItems: 'center', gap: 6 } },
56
+ selectedOrg ? selectedOrg.name : 'Your Organization',
57
+ h('span', { className: 'badge badge-neutral', style: { fontSize: 10 } }, 'Locked')
58
+ )
59
+ : h('select', {
60
+ value: selectedOrgId,
61
+ onChange: function(e) {
62
+ var id = e.target.value;
63
+ var org = orgs.find(function(o) { return o.id === id; });
64
+ onOrgChange(id, org || null);
65
+ },
66
+ style: {
67
+ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--border)',
68
+ background: 'var(--bg-card)', color: 'var(--text)', fontSize: 13,
69
+ cursor: 'pointer', fontWeight: 600, flex: 1, maxWidth: 300
70
+ }
71
+ },
72
+ h('option', { value: '' }, 'My Organization'),
73
+ orgs.filter(function(o) { return o.is_active !== false; }).map(function(o) {
74
+ return h('option', { key: o.id, value: o.id }, o.name + (o.billing_rate_per_agent > 0 ? ' (' + (o.currency || 'USD') + ' ' + parseFloat(o.billing_rate_per_agent).toFixed(0) + '/agent)' : ''));
75
+ })
76
+ ),
77
+ selectedOrg && h('span', { style: { fontSize: 11, color: 'var(--text-muted)' } },
78
+ selectedOrg.contact_name ? selectedOrg.contact_name : '',
79
+ selectedOrg.contact_email ? ' \u2022 ' + selectedOrg.contact_email : ''
80
+ ),
81
+ // Impersonation banner
82
+ app.impersonating && h('span', { className: 'badge badge-warning', style: { fontSize: 10, marginLeft: 'auto' } }, 'Impersonating: ' + (user.name || user.email))
83
+ );
84
+ }
85
+
86
+ /**
87
+ * useOrgContext — Hook that provides org switching state.
88
+ * Auto-selects the user's client org if they are org-bound.
89
+ */
90
+ export function useOrgContext() {
91
+ var app = useApp();
92
+ var user = app.user || {};
93
+ var userOrgId = user.clientOrgId || '';
94
+
95
+ var _sel = useState(userOrgId);
96
+ var selectedOrgId = _sel[0]; var setSelectedOrgId = _sel[1];
97
+ var _org = useState(null);
98
+ var selectedOrg = _org[0]; var setSelectedOrg = _org[1];
99
+
100
+ // If user changes (e.g. impersonation), update default
101
+ useEffect(function() {
102
+ if (userOrgId && !selectedOrgId) setSelectedOrgId(userOrgId);
103
+ }, [userOrgId]);
104
+
105
+ var onOrgChange = function(id, org) {
106
+ setSelectedOrgId(id);
107
+ setSelectedOrg(org);
108
+ };
109
+
110
+ var Switcher = function(extraProps) {
111
+ return h(OrgContextSwitcher, Object.assign({
112
+ selectedOrgId: selectedOrgId,
113
+ onOrgChange: onOrgChange
114
+ }, extraProps || {}));
115
+ };
116
+
117
+ return { selectedOrgId: selectedOrgId, selectedOrg: selectedOrg, onOrgChange: onOrgChange, Switcher: Switcher };
118
+ }
@@ -3,6 +3,7 @@ import { I } from '../components/icons.js';
3
3
  import { E } from '../assets/icons/emoji-icons.js';
4
4
  import { CULTURES, LANGUAGES, PersonaForm } from '../components/persona-fields.js';
5
5
  import { HelpButton } from '../components/help-button.js';
6
+ import { useOrgContext } from '../components/org-switcher.js';
6
7
 
7
8
  // ════════════════════════════════════════════════════════════
8
9
  // DEPLOY MODAL
@@ -1127,6 +1128,7 @@ export function CreateAgentWizard({ onClose, onCreated, toast }) {
1127
1128
  export function AgentsPage({ onSelectAgent }) {
1128
1129
  const app = useApp();
1129
1130
  const toast = app.toast;
1131
+ var orgCtx = useOrgContext();
1130
1132
  const [agents, setAgents] = useState([]);
1131
1133
  const [creating, setCreating] = useState(false);
1132
1134
 
@@ -1138,9 +1140,13 @@ export function AgentsPage({ onSelectAgent }) {
1138
1140
  if (allowedAgents !== '*' && Array.isArray(allowedAgents)) {
1139
1141
  all = all.filter(a => allowedAgents.indexOf(a.id) >= 0);
1140
1142
  }
1143
+ // Filter by selected org context
1144
+ if (orgCtx.selectedOrgId) {
1145
+ all = all.filter(a => a.client_org_id === orgCtx.selectedOrgId);
1146
+ }
1141
1147
  setAgents(all);
1142
1148
  }).catch(() => {});
1143
- useEffect(() => { load(); }, []);
1149
+ useEffect(() => { load(); }, [orgCtx.selectedOrgId]);
1144
1150
 
1145
1151
  // Delete moved to agent detail overview tab with triple confirmation
1146
1152
 
@@ -1149,6 +1155,7 @@ export function AgentsPage({ onSelectAgent }) {
1149
1155
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
1150
1156
 
1151
1157
  return h(Fragment, null,
1158
+ h(orgCtx.Switcher),
1152
1159
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } },
1153
1160
  h('div', null, h('h1', { style: { fontSize: 20, fontWeight: 700, display: 'flex', alignItems: 'center' } }, 'Agents', h(HelpButton, { label: 'Agents' },
1154
1161
  h('p', null, 'Your AI workforce. Each agent has its own email identity, personality, skills, permissions, and deployment target.'),
@@ -1,8 +1,11 @@
1
1
  import { h, useState, useEffect, Fragment, useApp, engineCall, showConfirm, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
+ import { useOrgContext } from '../components/org-switcher.js';
4
5
 
5
6
  export function ApprovalsPage() {
7
+ var orgCtx = useOrgContext();
8
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
6
9
  const { toast } = useApp();
7
10
  const [pending, setPending] = useState([]);
8
11
  const [history, setHistory] = useState([]);
@@ -13,7 +16,7 @@ export function ApprovalsPage() {
13
16
  const load = () => {
14
17
  engineCall('/approvals/pending').then(d => setPending(d.requests || [])).catch(() => {});
15
18
  engineCall('/approvals/history?limit=50').then(d => setHistory(d.requests || [])).catch(() => {});
16
- engineCall('/agents?orgId=' + getOrgId()).then(d => setAgents(d.agents || [])).catch(() => {});
19
+ engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
17
20
  };
18
21
  useEffect(() => { load(); }, []);
19
22
 
@@ -33,6 +36,7 @@ export function ApprovalsPage() {
33
36
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
34
37
 
35
38
  return h(Fragment, null,
39
+ h(orgCtx.Switcher),
36
40
  h('div', { style: { marginBottom: 20 } },
37
41
  h('h1', { style: { fontSize: 20, fontWeight: 700, display: 'flex', alignItems: 'center' } }, 'Approvals', h(HelpButton, { label: 'Approvals' },
38
42
  h('p', null, 'The human-in-the-loop checkpoint. When agents attempt sensitive actions (based on your permission settings), they pause and wait for your approval here.'),
@@ -2,6 +2,7 @@ import { h, useState, useEffect, Fragment, buildAgentEmailMap, buildAgentDataMap
2
2
  import { I } from '../components/icons.js';
3
3
  import { DetailModal } from '../components/modal.js';
4
4
  import { HelpButton } from '../components/help-button.js';
5
+ import { useOrgContext } from '../components/org-switcher.js';
5
6
 
6
7
  export function SetupChecklist({ onNavigate }) {
7
8
  const [status, setStatus] = useState(null);
@@ -46,6 +47,8 @@ export function SetupChecklist({ onNavigate }) {
46
47
  }
47
48
 
48
49
  export function DashboardPage() {
50
+ var orgCtx = useOrgContext();
51
+ var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
49
52
  const [stats, setStats] = useState(null);
50
53
  const [agents, setAgents] = useState([]);
51
54
  const [events, setEvents] = useState([]);
@@ -58,9 +61,9 @@ export function DashboardPage() {
58
61
  useEffect(() => {
59
62
  apiCall('/stats').then(setStats).catch(() => {});
60
63
  apiCall('/agents').then(d => setAgents(d.agents || d || [])).catch(() => {});
61
- engineCall('/agents?orgId=' + getOrgId()).then(d => setEngineAgents(d.agents || [])).catch(() => {});
64
+ engineCall('/agents?orgId=' + effectiveOrgId).then(d => setEngineAgents(d.agents || [])).catch(() => {});
62
65
  engineCall('/activity/events?limit=10').then(d => setEvents(d.events || [])).catch(() => {});
63
- }, []);
66
+ }, [effectiveOrgId]);
64
67
 
65
68
  // Merge admin + engine agents; engine agents (appended last) win in the data map
66
69
  var mergedForMap = [].concat(agents, engineAgents);
@@ -73,6 +76,7 @@ export function DashboardPage() {
73
76
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
74
77
 
75
78
  return h(Fragment, null,
79
+ h(orgCtx.Switcher),
76
80
  h(SetupChecklist, { onNavigate: function(pg) { if (navTo) navTo(pg); } }),
77
81
  h('div', { className: 'stat-grid' },
78
82
  h('div', { className: 'stat-card' }, h('div', { className: 'stat-label', style: { display: 'flex', alignItems: 'center' } }, 'Total Agents', h(HelpButton, { label: 'Total Agents' },
@@ -1,6 +1,7 @@
1
1
  import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
+ import { useOrgContext } from '../components/org-switcher.js';
4
5
 
5
6
  // ─── Constants ──────────────────────────────────────────
6
7
 
@@ -93,6 +94,8 @@ function EmptyState(props) {
93
94
  // ─── Main Page ──────────────────────────────────────────
94
95
 
95
96
  export function GuardrailsPage() {
97
+ var orgCtx = useOrgContext();
98
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
96
99
  var app = useApp();
97
100
  var toast = app.toast;
98
101
  var tab = useState('overview');
@@ -101,7 +104,7 @@ export function GuardrailsPage() {
101
104
  var _ag = useState([]);
102
105
  var agents = _ag[0]; var setAgents = _ag[1];
103
106
  useEffect(function() {
104
- engineCall('/agents?orgId=' + getOrgId()).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
107
+ engineCall('/agents?orgId=' + effectiveOrgId).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
105
108
  }, []);
106
109
 
107
110
  var TABS = [
@@ -164,8 +167,8 @@ function OverviewTab(props) {
164
167
  var load = function() {
165
168
  setLoading(true);
166
169
  Promise.all([
167
- engineCall('/guardrails/interventions?orgId=' + getOrgId() + '&limit=10').catch(function() { return { interventions: [] }; }),
168
- engineCall('/policies?orgId=' + getOrgId()).catch(function() { return { policies: [] }; }),
170
+ engineCall('/guardrails/interventions?orgId=' + effectiveOrgId + '&limit=10').catch(function() { return { interventions: [] }; }),
171
+ engineCall('/policies?orgId=' + effectiveOrgId).catch(function() { return { policies: [] }; }),
169
172
  engineCall('/onboarding/org/default').catch(function() { return { progress: [] }; }),
170
173
  ]).then(function(res) {
171
174
  setInterventions(res[0].interventions || []);
@@ -203,6 +206,7 @@ function OverviewTab(props) {
203
206
  var typeColor = function(t) { return t === 'kill' ? '#ef4444' : t === 'pause' ? '#f59e0b' : t === 'resume' ? '#15803d' : '#0ea5e9'; };
204
207
 
205
208
  return h(Fragment, null,
209
+ h(orgCtx.Switcher),
206
210
  // Quick action bar
207
211
  h('div', { className: 'card', style: { marginBottom: 16 } },
208
212
  h('div', { className: 'card-body', style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' } },
@@ -282,17 +286,17 @@ function PoliciesTab() {
282
286
  var editPolicy = _edit[0]; var setEditPolicy = _edit[1];
283
287
  var _exp = useState(null);
284
288
  var expanded = _exp[0]; var setExpanded = _exp[1];
285
- var _form = useState({ orgId: getOrgId(), name: '', category: 'code_of_conduct', description: '', content: '', priority: 0, enforcement: 'mandatory', appliesTo: ['*'], tags: [], enabled: true });
289
+ var _form = useState({ orgId: effectiveOrgId, name: '', category: 'code_of_conduct', description: '', content: '', priority: 0, enforcement: 'mandatory', appliesTo: ['*'], tags: [], enabled: true });
286
290
  var form = _form[0]; var setForm = _form[1];
287
291
 
288
292
  var load = function() {
289
- engineCall('/policies?orgId=' + getOrgId()).then(function(d) { setPolicies(d.policies || []); }).catch(function() {});
293
+ engineCall('/policies?orgId=' + effectiveOrgId).then(function(d) { setPolicies(d.policies || []); }).catch(function() {});
290
294
  };
291
295
  useEffect(load, []);
292
296
 
293
297
  var openCreate = function() {
294
298
  setEditPolicy(null);
295
- setForm({ orgId: getOrgId(), name: '', category: 'code_of_conduct', description: '', content: '', priority: 0, enforcement: 'mandatory', appliesTo: ['*'], tags: [], enabled: true });
299
+ setForm({ orgId: effectiveOrgId, name: '', category: 'code_of_conduct', description: '', content: '', priority: 0, enforcement: 'mandatory', appliesTo: ['*'], tags: [], enabled: true });
296
300
  setShowModal(true);
297
301
  };
298
302
  var openEdit = function(p) {
@@ -314,7 +318,7 @@ function PoliciesTab() {
314
318
  .catch(function(e) { toast(e.message, 'error'); });
315
319
  };
316
320
  var applyDefaults = function() {
317
- engineCall('/policies/templates/apply', { method: 'POST', body: JSON.stringify({ orgId: getOrgId(), createdBy: 'admin' }) })
321
+ engineCall('/policies/templates/apply', { method: 'POST', body: JSON.stringify({ orgId: effectiveOrgId, createdBy: 'admin' }) })
318
322
  .then(function(d) { toast('Applied ' + (d.policies ? d.policies.length : 0) + ' default templates', 'success'); load(); })
319
323
  .catch(function(e) { toast(e.message, 'error'); });
320
324
  };
@@ -451,7 +455,7 @@ function OnboardingTab(props) {
451
455
 
452
456
  var initiate = function() {
453
457
  if (!initAgentId) { toast('Enter an agent ID', 'error'); return; }
454
- engineCall('/onboarding/initiate/' + initAgentId, { method: 'POST', body: JSON.stringify({ orgId: getOrgId() }) })
458
+ engineCall('/onboarding/initiate/' + initAgentId, { method: 'POST', body: JSON.stringify({ orgId: effectiveOrgId }) })
455
459
  .then(function() { toast('Onboarding initiated', 'success'); setInitAgentId(''); load(); })
456
460
  .catch(function(e) { toast(e.message, 'error'); });
457
461
  };
@@ -461,7 +465,7 @@ function OnboardingTab(props) {
461
465
  .catch(function(e) { toast(e.message, 'error'); });
462
466
  };
463
467
  var checkChanges = function() {
464
- engineCall('/onboarding/check-changes', { method: 'POST', body: JSON.stringify({ orgId: getOrgId() }) })
468
+ engineCall('/onboarding/check-changes', { method: 'POST', body: JSON.stringify({ orgId: effectiveOrgId }) })
465
469
  .then(function(d) {
466
470
  var stale = d.staleAgents || [];
467
471
  if (stale.length === 0) { toast('All agents up to date', 'success'); }
@@ -555,7 +559,7 @@ function MemoryTab(props) {
555
559
  var showCreate = _show[0]; var setShowCreate = _show[1];
556
560
  var _exp = useState(null);
557
561
  var expanded = _exp[0]; var setExpanded = _exp[1];
558
- var _form = useState({ agentId: '', orgId: getOrgId(), category: 'org_knowledge', title: '', content: '', source: 'admin', importance: 'normal', tags: [] });
562
+ var _form = useState({ agentId: '', orgId: effectiveOrgId, category: 'org_knowledge', title: '', content: '', source: 'admin', importance: 'normal', tags: [] });
559
563
  var form = _form[0]; var setForm = _form[1];
560
564
 
561
565
  var loadMemories = function(aid) {
@@ -762,13 +766,13 @@ function RulesTab(props) {
762
766
  var _showAnomaly = useState(false);
763
767
  var showAnomalyModal = _showAnomaly[0]; var setShowAnomalyModal = _showAnomaly[1];
764
768
  var _form = useState({
765
- orgId: getOrgId(), name: '', description: '', category: 'anomaly', ruleType: 'threshold',
769
+ orgId: effectiveOrgId, name: '', description: '', category: 'anomaly', ruleType: 'threshold',
766
770
  conditions: { threshold: 10, windowMinutes: 60 },
767
771
  action: 'alert', severity: 'medium', cooldownMinutes: 15, enabled: true
768
772
  });
769
773
  var form = _form[0]; var setForm = _form[1];
770
774
  var _anomalyForm = useState({
771
- orgId: getOrgId(), name: '', ruleType: 'error_rate',
775
+ orgId: effectiveOrgId, name: '', ruleType: 'error_rate',
772
776
  config: { maxErrorsPerHour: 50, windowMinutes: 60 }, action: 'pause', enabled: true
773
777
  });
774
778
  var anomalyForm = _anomalyForm[0]; var setAnomalyForm = _anomalyForm[1];
@@ -777,9 +781,9 @@ function RulesTab(props) {
777
781
 
778
782
  var load = function() {
779
783
  Promise.all([
780
- engineCall('/guardrails/rules?orgId=' + getOrgId()).catch(function() { return { rules: [] }; }),
781
- engineCall('/anomaly-rules?orgId=' + getOrgId()).catch(function() { return { rules: [] }; }),
782
- engineCall('/guardrails/interventions?orgId=' + getOrgId() + '&limit=50').catch(function() { return { interventions: [] }; }),
784
+ engineCall('/guardrails/rules?orgId=' + effectiveOrgId).catch(function() { return { rules: [] }; }),
785
+ engineCall('/anomaly-rules?orgId=' + effectiveOrgId).catch(function() { return { rules: [] }; }),
786
+ engineCall('/guardrails/interventions?orgId=' + effectiveOrgId + '&limit=50').catch(function() { return { interventions: [] }; }),
783
787
  ]).then(function(res) {
784
788
  setRules(res[0].rules || []);
785
789
  setAnomalyRules(res[1].rules || []);
@@ -791,7 +795,7 @@ function RulesTab(props) {
791
795
  // Guardrail rules CRUD
792
796
  var openCreateRule = function() {
793
797
  setEditRule(null);
794
- setForm({ orgId: getOrgId(), name: '', description: '', category: 'anomaly', ruleType: 'threshold', conditions: { threshold: 10, windowMinutes: 60 }, action: 'alert', severity: 'medium', cooldownMinutes: 15, enabled: true });
798
+ setForm({ orgId: effectiveOrgId, name: '', description: '', category: 'anomaly', ruleType: 'threshold', conditions: { threshold: 10, windowMinutes: 60 }, action: 'alert', severity: 'medium', cooldownMinutes: 15, enabled: true });
795
799
  setShowModal(true);
796
800
  };
797
801
  var openEditRule = function(r) {
@@ -1,8 +1,11 @@
1
1
  import { h, useState, useEffect, Fragment, useApp, engineCall, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
+ import { useOrgContext } from '../components/org-switcher.js';
4
5
 
5
6
  export function JournalPage() {
7
+ var orgCtx = useOrgContext();
8
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
6
9
  const { toast } = useApp();
7
10
  const [entries, setEntries] = useState([]);
8
11
  const [total, setTotal] = useState(0);
@@ -11,9 +14,9 @@ export function JournalPage() {
11
14
  const [agents, setAgents] = useState([]);
12
15
 
13
16
  const load = () => {
14
- engineCall('/journal?orgId=' + getOrgId() + '&limit=50').then(d => { setEntries(d.entries || []); setTotal(d.total || 0); }).catch(() => {});
17
+ engineCall('/journal?orgId=' + effectiveOrgId + '&limit=50').then(d => { setEntries(d.entries || []); setTotal(d.total || 0); }).catch(() => {});
15
18
  engineCall('/journal/stats/default').then(d => setStats(d)).catch(() => {});
16
- engineCall('/agents?orgId=' + getOrgId()).then(d => setAgents(d.agents || [])).catch(() => {});
19
+ engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
17
20
  };
18
21
  useEffect(load, []);
19
22
 
@@ -29,6 +32,7 @@ export function JournalPage() {
29
32
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
30
33
 
31
34
  return h('div', { className: 'page-inner' },
35
+ h(orgCtx.Switcher),
32
36
  h('div', { className: 'page-header' }, h('h1', { style: { display: 'flex', alignItems: 'center' } }, 'Action Journal', h(HelpButton, { label: 'Action Journal' },
33
37
  h('p', null, 'A tamper-proof log of every action agents have taken. Think of it as an audit trail — every tool call, every side effect, recorded with full context.'),
34
38
  h('h4', { style: _h4 }, 'Why it matters'),
@@ -2,9 +2,11 @@ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buil
2
2
  import { I } from '../components/icons.js';
3
3
  import { Modal } from '../components/modal.js';
4
4
  import { HelpButton } from '../components/help-button.js';
5
+ import { useOrgContext } from '../components/org-switcher.js';
5
6
 
6
7
  export function KnowledgeContributionsPage() {
7
8
  var { toast } = useApp();
9
+ var orgCtx = useOrgContext();
8
10
  var [tab, setTab] = useState('bases');
9
11
  var [bases, setBases] = useState([]);
10
12
  var [roles, setRoles] = useState([]);
@@ -39,14 +41,17 @@ export function KnowledgeContributionsPage() {
39
41
  var [searchDays, setSearchDays] = useState(7);
40
42
  var [searchAgentFilter, setSearchAgentFilter] = useState('');
41
43
 
44
+ // Effective org ID: uses client org if selected, else default
45
+ var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
46
+
42
47
  var loadBases = useCallback(function() {
43
48
  Promise.all([
44
- engineCall('/knowledge-contribution/bases?orgId=' + getOrgId()).catch(function() { return { bases: [] }; }),
49
+ engineCall('/knowledge-contribution/bases?orgId=' + effectiveOrgId).catch(function() { return { bases: [] }; }),
45
50
  engineCall('/knowledge-bases').catch(function() { return { knowledgeBases: [] }; })
46
51
  ]).then(function(results) {
47
52
  var contribBases = results[0].bases || [];
48
53
  var mainBases = (results[1].knowledgeBases || []).map(function(kb) {
49
- return { id: kb.id, orgId: getOrgId(), name: kb.name, description: kb.description, role: 'general', categories: [], contributorCount: 0, entryCount: kb.stats ? kb.stats.documentCount || 0 : 0, createdAt: kb.createdAt, updatedAt: kb.updatedAt, _source: 'main' };
54
+ return { id: kb.id, orgId: effectiveOrgId, name: kb.name, description: kb.description, role: 'general', categories: [], contributorCount: 0, entryCount: kb.stats ? kb.stats.documentCount || 0 : 0, createdAt: kb.createdAt, updatedAt: kb.updatedAt, _source: 'main' };
50
55
  });
51
56
  // Merge: contribution bases first, then main bases not already in contribution
52
57
  var ids = {};
@@ -63,19 +68,19 @@ export function KnowledgeContributionsPage() {
63
68
  }, []);
64
69
 
65
70
  var loadStats = useCallback(function() {
66
- engineCall('/knowledge-contribution/stats?orgId=' + getOrgId())
71
+ engineCall('/knowledge-contribution/stats?orgId=' + effectiveOrgId)
67
72
  .then(function(d) { setStats(d || {}); })
68
73
  .catch(function() {});
69
74
  }, []);
70
75
 
71
76
  var loadContributions = useCallback(function() {
72
- engineCall('/knowledge-contribution/contributions?orgId=' + getOrgId())
77
+ engineCall('/knowledge-contribution/contributions?orgId=' + effectiveOrgId)
73
78
  .then(function(d) { setContributions(d.contributions || d.cycles || []); })
74
79
  .catch(function() {});
75
80
  }, []);
76
81
 
77
82
  var loadSchedules = useCallback(function() {
78
- engineCall('/knowledge-contribution/schedules?orgId=' + getOrgId())
83
+ engineCall('/knowledge-contribution/schedules?orgId=' + effectiveOrgId)
79
84
  .then(function(d) { setSchedules(d.schedules || []); })
80
85
  .catch(function() {});
81
86
  }, []);
@@ -86,10 +91,10 @@ export function KnowledgeContributionsPage() {
86
91
  loadStats();
87
92
  loadContributions();
88
93
  loadSchedules();
89
- engineCall('/agents?orgId=' + getOrgId()).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
94
+ engineCall('/agents?orgId=' + effectiveOrgId).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
90
95
  }, [loadBases, loadRoles, loadStats, loadContributions, loadSchedules]);
91
96
 
92
- useEffect(function() { load(); }, [load]);
97
+ useEffect(function() { load(); }, [load, effectiveOrgId]);
93
98
 
94
99
  var loadBaseEntries = useCallback(function(baseId) {
95
100
  var params = new URLSearchParams();
@@ -110,7 +115,7 @@ export function KnowledgeContributionsPage() {
110
115
  try {
111
116
  await engineCall('/knowledge-contribution/bases', {
112
117
  method: 'POST',
113
- body: JSON.stringify({ name: baseForm.name, description: baseForm.description, role: baseForm.role, orgId: getOrgId() })
118
+ body: JSON.stringify({ name: baseForm.name, description: baseForm.description, role: baseForm.role, orgId: effectiveOrgId })
114
119
  });
115
120
  toast('Knowledge base created', 'success');
116
121
  setShowCreateBase(false);
@@ -161,7 +166,7 @@ export function KnowledgeContributionsPage() {
161
166
  try {
162
167
  await engineCall('/knowledge-contribution/contribute/' + triggerAgent, {
163
168
  method: 'POST',
164
- body: JSON.stringify({ targetBaseId: triggerBase || undefined, orgId: getOrgId() })
169
+ body: JSON.stringify({ targetBaseId: triggerBase || undefined, orgId: effectiveOrgId })
165
170
  });
166
171
  toast('Contribution triggered', 'success');
167
172
  setShowTrigger(false);
@@ -182,7 +187,7 @@ export function KnowledgeContributionsPage() {
182
187
  frequency: scheduleForm.frequency,
183
188
  dayOfWeek: scheduleForm.dayOfWeek,
184
189
  minConfidence: parseFloat(scheduleForm.minConfidence) || 0.7,
185
- orgId: getOrgId()
190
+ orgId: effectiveOrgId
186
191
  })
187
192
  });
188
193
  toast('Schedule created', 'success');
@@ -1375,6 +1380,9 @@ export function KnowledgeContributionsPage() {
1375
1380
  };
1376
1381
 
1377
1382
  return h(Fragment, null,
1383
+ // Org context switcher
1384
+ h(orgCtx.Switcher),
1385
+
1378
1386
  // Header
1379
1387
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 } },
1380
1388
  h('div', null,