@agenticmail/enterprise 0.5.304 → 0.5.306

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,143 @@
1
+ import "./chunk-KFQGP6VL.js";
2
+
3
+ // src/cli-serve.ts
4
+ import { existsSync, readFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ function loadEnvFile() {
8
+ const candidates = [
9
+ join(process.cwd(), ".env"),
10
+ join(homedir(), ".agenticmail", ".env")
11
+ ];
12
+ for (const envPath of candidates) {
13
+ if (!existsSync(envPath)) continue;
14
+ try {
15
+ const content = readFileSync(envPath, "utf8");
16
+ for (const line of content.split("\n")) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed || trimmed.startsWith("#")) continue;
19
+ const eq = trimmed.indexOf("=");
20
+ if (eq < 0) continue;
21
+ const key = trimmed.slice(0, eq).trim();
22
+ let val = trimmed.slice(eq + 1).trim();
23
+ if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
24
+ val = val.slice(1, -1);
25
+ }
26
+ if (!process.env[key]) process.env[key] = val;
27
+ }
28
+ console.log(`Loaded config from ${envPath}`);
29
+ return;
30
+ } catch {
31
+ }
32
+ }
33
+ }
34
+ async function ensureSecrets() {
35
+ const { randomUUID } = await import("crypto");
36
+ const envDir = join(homedir(), ".agenticmail");
37
+ const envPath = join(envDir, ".env");
38
+ let dirty = false;
39
+ if (!process.env.JWT_SECRET) {
40
+ process.env.JWT_SECRET = randomUUID() + randomUUID();
41
+ dirty = true;
42
+ console.log("[startup] Generated new JWT_SECRET (existing sessions will need to re-login)");
43
+ }
44
+ if (!process.env.AGENTICMAIL_VAULT_KEY) {
45
+ process.env.AGENTICMAIL_VAULT_KEY = randomUUID() + randomUUID();
46
+ dirty = true;
47
+ console.log("[startup] Generated new AGENTICMAIL_VAULT_KEY");
48
+ console.log("[startup] \u26A0\uFE0F Previously encrypted credentials will need to be re-entered in the dashboard");
49
+ }
50
+ if (dirty) {
51
+ try {
52
+ if (!existsSync(envDir)) {
53
+ const { mkdirSync } = await import("fs");
54
+ mkdirSync(envDir, { recursive: true });
55
+ }
56
+ const { appendFileSync } = await import("fs");
57
+ const lines = [];
58
+ let existing = "";
59
+ if (existsSync(envPath)) {
60
+ existing = readFileSync(envPath, "utf8");
61
+ }
62
+ if (!existing.includes("JWT_SECRET=")) {
63
+ lines.push(`JWT_SECRET=${process.env.JWT_SECRET}`);
64
+ }
65
+ if (!existing.includes("AGENTICMAIL_VAULT_KEY=")) {
66
+ lines.push(`AGENTICMAIL_VAULT_KEY=${process.env.AGENTICMAIL_VAULT_KEY}`);
67
+ }
68
+ if (lines.length) {
69
+ appendFileSync(envPath, "\n" + lines.join("\n") + "\n", { mode: 384 });
70
+ console.log(`[startup] Saved secrets to ${envPath}`);
71
+ }
72
+ } catch (e) {
73
+ console.warn(`[startup] Could not save secrets to ${envPath}: ${e.message}`);
74
+ }
75
+ }
76
+ }
77
+ async function runServe(_args) {
78
+ loadEnvFile();
79
+ const DATABASE_URL = process.env.DATABASE_URL;
80
+ const PORT = parseInt(process.env.PORT || "8080", 10);
81
+ await ensureSecrets();
82
+ const JWT_SECRET = process.env.JWT_SECRET;
83
+ const VAULT_KEY = process.env.AGENTICMAIL_VAULT_KEY;
84
+ if (!DATABASE_URL) {
85
+ console.error("ERROR: DATABASE_URL is required.");
86
+ console.error("");
87
+ console.error("Set it via environment variable or .env file:");
88
+ console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db npx @agenticmail/enterprise start");
89
+ console.error("");
90
+ console.error("Or create a .env file (in cwd or ~/.agenticmail/.env):");
91
+ console.error(" DATABASE_URL=postgresql://user:pass@host:5432/db");
92
+ console.error(" JWT_SECRET=your-secret-here");
93
+ console.error(" PORT=3200");
94
+ process.exit(1);
95
+ }
96
+ const { createAdapter } = await import("./factory-QYGGXVYW.js");
97
+ const { createServer } = await import("./server-43HP7BAB.js");
98
+ const db = await createAdapter({
99
+ type: DATABASE_URL.startsWith("postgres") ? "postgres" : "sqlite",
100
+ connectionString: DATABASE_URL
101
+ });
102
+ await db.migrate();
103
+ const server = createServer({
104
+ port: PORT,
105
+ db,
106
+ jwtSecret: JWT_SECRET,
107
+ corsOrigins: ["*"]
108
+ });
109
+ await server.start();
110
+ console.log(`AgenticMail Enterprise server running on :${PORT}`);
111
+ const tunnelToken = process.env.CLOUDFLARED_TOKEN;
112
+ if (tunnelToken) {
113
+ try {
114
+ const { execSync, spawn } = await import("child_process");
115
+ try {
116
+ execSync("which cloudflared", { timeout: 3e3 });
117
+ } catch {
118
+ console.log("[startup] cloudflared not found \u2014 skipping tunnel auto-start");
119
+ console.log("[startup] Install cloudflared to enable tunnel: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/");
120
+ return;
121
+ }
122
+ try {
123
+ execSync('pgrep -f "cloudflared.*tunnel.*run"', { timeout: 3e3 });
124
+ console.log("[startup] cloudflared tunnel already running");
125
+ return;
126
+ } catch {
127
+ }
128
+ const subdomain = process.env.AGENTICMAIL_SUBDOMAIN || process.env.AGENTICMAIL_DOMAIN || "";
129
+ console.log(`[startup] Starting cloudflared tunnel${subdomain ? ` for ${subdomain}.agenticmail.io` : ""}...`);
130
+ const child = spawn("cloudflared", ["tunnel", "--no-autoupdate", "run", "--token", tunnelToken], {
131
+ detached: true,
132
+ stdio: "ignore"
133
+ });
134
+ child.unref();
135
+ console.log("[startup] cloudflared tunnel started (pid " + child.pid + ")");
136
+ } catch (e) {
137
+ console.warn("[startup] Could not auto-start cloudflared: " + e.message);
138
+ }
139
+ }
140
+ }
141
+ export {
142
+ runServe
143
+ };
package/dist/cli.js CHANGED
@@ -57,14 +57,14 @@ Skill Development:
57
57
  break;
58
58
  case "serve":
59
59
  case "start":
60
- import("./cli-serve-JZEXPYXY.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
60
+ import("./cli-serve-KZT4WNKR.js").then((m) => m.runServe(args.slice(1))).catch(fatal);
61
61
  break;
62
62
  case "agent":
63
63
  import("./cli-agent-YSQVDBOZ.js").then((m) => m.runAgent(args.slice(1))).catch(fatal);
64
64
  break;
65
65
  case "setup":
66
66
  default:
67
- import("./setup-ZZAOBEY4.js").then((m) => m.runSetupWizard()).catch(fatal);
67
+ import("./setup-2NQBX5FF.js").then((m) => m.runSetupWizard()).catch(fatal);
68
68
  break;
69
69
  }
70
70
  function fatal(err) {
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, Fragment, useApp, engineCall, showConfirm, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, Fragment, useApp, engineCall, showConfirm, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -16,7 +16,7 @@ export function ApprovalsPage() {
16
16
  const load = () => {
17
17
  engineCall('/approvals/pending').then(d => setPending(d.requests || [])).catch(() => {});
18
18
  engineCall('/approvals/history?limit=50').then(d => setHistory(d.requests || [])).catch(() => {});
19
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
19
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(d => setAgents(d.agents || [])).catch(() => {});
20
20
  };
21
21
  useEffect(() => { load(); }, []);
22
22
 
@@ -48,7 +48,7 @@ export function SetupChecklist({ onNavigate }) {
48
48
 
49
49
  export function DashboardPage() {
50
50
  var orgCtx = useOrgContext();
51
- var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
51
+ var clientOrgFilter = orgCtx.selectedOrgId || '';
52
52
  const [stats, setStats] = useState(null);
53
53
  const [agents, setAgents] = useState([]);
54
54
  const [events, setEvents] = useState([]);
@@ -59,11 +59,12 @@ export function DashboardPage() {
59
59
  var selectedEvent = _selectedEvent[0]; var setSelectedEvent = _selectedEvent[1];
60
60
 
61
61
  useEffect(() => {
62
+ var agentUrl = clientOrgFilter ? '/agents?clientOrgId=' + clientOrgFilter : '/agents';
62
63
  apiCall('/stats').then(setStats).catch(() => {});
63
- apiCall('/agents').then(d => setAgents(d.agents || d || [])).catch(() => {});
64
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setEngineAgents(d.agents || [])).catch(() => {});
64
+ apiCall(agentUrl).then(d => setAgents(d.agents || d || [])).catch(() => {});
65
+ engineCall('/agents?orgId=' + getOrgId()).then(d => setEngineAgents(d.agents || [])).catch(() => {});
65
66
  engineCall('/activity/events?limit=10').then(d => setEvents(d.events || [])).catch(() => {});
66
- }, [effectiveOrgId]);
67
+ }, [clientOrgFilter]);
67
68
 
68
69
  // Merge admin + engine agents; engine agents (appended last) win in the data map
69
70
  var mergedForMap = [].concat(agents, engineAgents);
@@ -81,7 +82,7 @@ export function DashboardPage() {
81
82
  h('div', { className: 'stat-grid' },
82
83
  h('div', { className: 'stat-card' }, h('div', { className: 'stat-label', style: { display: 'flex', alignItems: 'center' } }, 'Total Agents', h(HelpButton, { label: 'Total Agents' },
83
84
  h('p', null, 'The total number of agents created in your organization, including active, paused, and archived agents.')
84
- )), h('div', { className: 'stat-value' }, stats?.totalAgents ?? agents.length ?? '-')),
85
+ )), h('div', { className: 'stat-value' }, clientOrgFilter ? agents.length : (stats?.totalAgents ?? agents.length ?? '-'))),
85
86
  h('div', { className: 'stat-card' }, h('div', { className: 'stat-label', style: { display: 'flex', alignItems: 'center' } }, 'Active Agents', h(HelpButton, { label: 'Active Agents' },
86
87
  h('p', null, 'Agents currently running and available to process tasks. If this is lower than Total Agents, some agents may be paused or archived.')
87
88
  )), h('div', { className: 'stat-value', style: { color: 'var(--success)' } }, (stats?.activeAgents ?? agents.filter(function(a) { return a.status === 'active'; }).length) || '-')),
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -104,7 +104,7 @@ export function GuardrailsPage() {
104
104
  var _ag = useState([]);
105
105
  var agents = _ag[0]; var setAgents = _ag[1];
106
106
  useEffect(function() {
107
- engineCall('/agents?orgId=' + effectiveOrgId).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
107
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(function(d) { setAgents(d.agents || []); }).catch(function() {});
108
108
  }, []);
109
109
 
110
110
  var TABS = [
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, Fragment, useApp, engineCall, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, Fragment, useApp, engineCall, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -16,7 +16,7 @@ export function JournalPage() {
16
16
  const load = () => {
17
17
  engineCall('/journal?orgId=' + effectiveOrgId + '&limit=50').then(d => { setEntries(d.entries || []); setTotal(d.total || 0); }).catch(() => {});
18
18
  engineCall('/journal/stats/default').then(d => setStats(d)).catch(() => {});
19
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
19
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(d => setAgents(d.agents || [])).catch(() => {});
20
20
  };
21
21
  useEffect(load, []);
22
22
 
@@ -42,7 +42,7 @@ export function KnowledgeContributionsPage() {
42
42
  var [searchAgentFilter, setSearchAgentFilter] = useState('');
43
43
 
44
44
  // Effective org ID: uses client org if selected, else default
45
- var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
45
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
46
46
 
47
47
  var loadBases = useCallback(function() {
48
48
  Promise.all([
@@ -17,7 +17,7 @@ export function KnowledgeBasePage() {
17
17
  const [chunks, setChunks] = useState([]);
18
18
  const [selectedDoc, setSelectedDoc] = useState(null);
19
19
  const [editing, setEditing] = useState(false);
20
- const [editForm, setEditForm] = useState({ name: '', description: '' });
20
+ const [editForm, setEditForm] = useState({ name: '', description: '', clientOrgId: '' });
21
21
  const [loading, setLoading] = useState(false);
22
22
  const [showImport, setShowImport] = useState(false);
23
23
  const [importKb, setImportKb] = useState(null);
@@ -54,7 +54,7 @@ export function KnowledgeBasePage() {
54
54
  setDocs(kbData.documents || []);
55
55
  setChunks([]);
56
56
  setSelectedDoc(null);
57
- setEditForm({ name: kbData.name || '', description: kbData.description || '' });
57
+ setEditForm({ name: kbData.name || '', description: kbData.description || '', clientOrgId: kbData.clientOrgId || '' });
58
58
  } catch (e) {
59
59
  toast('Failed to load knowledge base: ' + e.message, 'error');
60
60
  }
@@ -122,9 +122,9 @@ export function KnowledgeBasePage() {
122
122
  const saveEdit = async () => {
123
123
  if (!selected) return;
124
124
  try {
125
- await engineCall('/knowledge-bases/' + selected.id, { method: 'PUT', body: JSON.stringify({ name: editForm.name, description: editForm.description }) });
125
+ await engineCall('/knowledge-bases/' + selected.id, { method: 'PUT', body: JSON.stringify({ name: editForm.name, description: editForm.description, clientOrgId: editForm.clientOrgId || null }) });
126
126
  toast('Knowledge base updated', 'success');
127
- setSelected(s => ({ ...s, name: editForm.name, description: editForm.description }));
127
+ setSelected(s => ({ ...s, name: editForm.name, description: editForm.description, clientOrgId: editForm.clientOrgId }));
128
128
  setEditing(false);
129
129
  load();
130
130
  } catch (e) { toast(e.message, 'error'); }
@@ -171,7 +171,16 @@ export function KnowledgeBasePage() {
171
171
  h('div', { className: 'card', style: { marginBottom: 16 } },
172
172
  h('div', { className: 'card-body' },
173
173
  editing
174
- ? h('textarea', { className: 'input', rows: 3, value: editForm.description, onChange: e => setEditForm(f => ({ ...f, description: e.target.value })), placeholder: 'Knowledge base description...' })
174
+ ? h(Fragment, null,
175
+ h('textarea', { className: 'input', rows: 3, value: editForm.description, onChange: e => setEditForm(f => ({ ...f, description: e.target.value })), placeholder: 'Knowledge base description...', style: { marginBottom: 8 } }),
176
+ clientOrgs.length > 0 && h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
177
+ h('label', { style: { fontSize: 12, fontWeight: 600, whiteSpace: 'nowrap' } }, 'Organization:'),
178
+ h('select', { className: 'input', value: editForm.clientOrgId, onChange: e => setEditForm(f => ({ ...f, clientOrgId: e.target.value })), style: { fontSize: 12, maxWidth: 250 } },
179
+ h('option', { value: '' }, 'My Organization (internal)'),
180
+ clientOrgs.filter(o => o.is_active !== false).map(o => h('option', { key: o.id, value: o.id }, o.name))
181
+ )
182
+ )
183
+ )
175
184
  : h('p', { style: { color: 'var(--text-secondary)', fontSize: 13, margin: 0 } }, selected.description || 'No description'),
176
185
  h('div', { style: { display: 'flex', gap: 12, marginTop: 12, fontSize: 12, color: 'var(--text-muted)' } },
177
186
  h('span', null, 'ID: ', h('code', null, selected.id)),
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, useRef, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useRef, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -22,7 +22,7 @@ export function MessagesPage() {
22
22
  engineCall('/messages?orgId=' + effectiveOrgId + '&limit=100').then(d => setMessages(d.messages || [])).catch(() => {});
23
23
  };
24
24
  const loadAgents = () => {
25
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
25
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(d => setAgents(d.agents || [])).catch(() => {});
26
26
  };
27
27
  const loadTopology = () => {
28
28
  engineCall('/messages/topology?orgId=' + effectiveOrgId).then(d => setTopology(d.topology || null)).catch(() => {});
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -154,7 +154,7 @@ export function OrgChartPage() {
154
154
  setLoading(true); setError(null);
155
155
  Promise.all([
156
156
  engineCall('/hierarchy/org-chart').catch(function() { return null; }),
157
- engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
157
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).catch(function() { return { agents: [] }; }),
158
158
  ]).then(function(res) {
159
159
  var hierRes = res[0]; var agentRes = res[1];
160
160
  var avatarMap = {};
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -440,7 +440,7 @@ export function TaskPipelinePage() {
440
440
  var app = useApp();
441
441
  var toast = app.toast;
442
442
  var orgCtx = useOrgContext();
443
- var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
443
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
444
444
  var _tasks = useState([]);
445
445
  var tasks = _tasks[0]; var setTasks = _tasks[1];
446
446
  var _stats = useState({ created: 0, assigned: 0, inProgress: 0, completed: 0, failed: 0, cancelled: 0, total: 0, todayCompleted: 0, todayFailed: 0, todayCreated: 0, avgDurationMs: 0, totalCost: 0, totalTokens: 0, topAgents: [] });
@@ -477,16 +477,26 @@ export function TaskPipelinePage() {
477
477
  Promise.all([
478
478
  engineCall('/task-pipeline?limit=200'),
479
479
  engineCall('/task-pipeline/stats'),
480
- engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
480
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).catch(function() { return { agents: [] }; }),
481
481
  ]).then(function(res) {
482
- setTasks(res[0]?.tasks || []);
483
- setStats(res[1] || stats);
482
+ var allTasks = res[0]?.tasks || [];
483
+ var orgAgents = res[2]?.agents || [];
484
484
  // Build agent avatar/name map
485
485
  var map = {};
486
- (res[2]?.agents || []).forEach(function(a) {
487
- map[a.id] = { name: a.config?.name || a.id, avatar: a.config?.identity?.avatar || a.config?.avatar || a.config?.persona?.avatar || null };
486
+ orgAgents.forEach(function(a) {
487
+ map[a.id] = { name: a.config?.name || a.name || a.id, avatar: a.config?.identity?.avatar || a.config?.avatar || a.config?.persona?.avatar || null };
488
488
  });
489
489
  setAgentMap(map);
490
+ // Filter tasks by org's agents when org is selected
491
+ if (orgCtx.selectedOrgId && orgAgents.length > 0) {
492
+ var agentIds = {};
493
+ orgAgents.forEach(function(a) { agentIds[a.id] = true; });
494
+ allTasks = allTasks.filter(function(t) { return agentIds[t.assignedAgent] || agentIds[t.createdBy]; });
495
+ } else if (orgCtx.selectedOrgId && orgAgents.length === 0) {
496
+ allTasks = []; // No agents in this org = no tasks
497
+ }
498
+ setTasks(allTasks);
499
+ setStats(res[1] || stats);
490
500
  }).catch(function(err) { console.error('[TaskPipeline]', err); })
491
501
  .finally(function() { setLoading(false); });
492
502
  }, [effectiveOrgId]);
@@ -235,6 +235,30 @@ function PermissionEditor({ userId, userName, currentPerms, pageRegistry, onSave
235
235
  })
236
236
  ),
237
237
 
238
+ // ─── Organization Assignment ──────────────────────
239
+ permOrgs.length > 0 && h('div', { style: { marginTop: 16, padding: '12px 14px', background: 'var(--bg-tertiary)', borderRadius: 8 } },
240
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 } },
241
+ h('div', null,
242
+ h('strong', { style: { fontSize: 13 } }, 'Client Organization'),
243
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 1 } }, 'Bind this user to a client org — they will only see that org\'s agents and data')
244
+ )
245
+ ),
246
+ h('select', {
247
+ className: 'input',
248
+ value: userOrgId,
249
+ onChange: function(e) { setUserOrgId(e.target.value); },
250
+ style: { fontSize: 13 }
251
+ },
252
+ h('option', { value: '' }, 'None (Internal User)'),
253
+ permOrgs.filter(function(o) { return o.is_active !== false; }).map(function(o) {
254
+ return h('option', { key: o.id, value: o.id }, o.name);
255
+ })
256
+ ),
257
+ userOrgId && h('div', { style: { fontSize: 11, color: 'var(--warning, #f59e0b)', marginTop: 6 } },
258
+ 'This user will only see agents and data belonging to this organization. The org switcher will be locked for them.'
259
+ )
260
+ ),
261
+
238
262
  // ─── Agent Access ──────────────────────────
239
263
  !fullAccess && h('div', { style: { marginTop: 16 } },
240
264
  h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 } },
@@ -470,6 +494,21 @@ export function UsersPage() {
470
494
  setResetting(false);
471
495
  };
472
496
 
497
+ var openEditUser = function(u) {
498
+ setEditUser(u);
499
+ setEditForm({ name: u.name || '', role: u.role || 'viewer', clientOrgId: u.clientOrgId || '' });
500
+ };
501
+
502
+ var doEditUser = async function() {
503
+ if (!editUser) return;
504
+ try {
505
+ await apiCall('/users/' + editUser.id, { method: 'PATCH', body: JSON.stringify({ name: editForm.name, role: editForm.role, clientOrgId: editForm.clientOrgId || null }) });
506
+ toast('User updated', 'success');
507
+ setEditUser(null);
508
+ load();
509
+ } catch (e) { toast(e.message || 'Update failed', 'error'); }
510
+ };
511
+
473
512
  var toggleActive = async function(user) {
474
513
  var action = user.isActive === false ? 'reactivate' : 'deactivate';
475
514
  var ok = await showConfirm({
@@ -491,6 +530,8 @@ export function UsersPage() {
491
530
  var [deleteStep, setDeleteStep] = useState(0);
492
531
  var [deleteTarget, setDeleteTarget] = useState(null);
493
532
  var [deleteTyped, setDeleteTyped] = useState('');
533
+ var [editUser, setEditUser] = useState(null);
534
+ var [editForm, setEditForm] = useState({ name: '', role: '', clientOrgId: '' });
494
535
 
495
536
  var startDelete = function(user) { setDeleteTarget(user); setDeleteStep(1); setDeleteTyped(''); };
496
537
  var cancelDelete = function() { setDeleteTarget(null); setDeleteStep(0); setDeleteTyped(''); };
@@ -631,13 +672,60 @@ export function UsersPage() {
631
672
  ),
632
673
 
633
674
  // Permission editor modal
675
+ // Edit User modal
676
+ editUser && h(Modal, {
677
+ title: 'Edit User — ' + (editUser.name || editUser.email),
678
+ onClose: function() { setEditUser(null); },
679
+ width: 420,
680
+ footer: h(Fragment, null,
681
+ h('button', { className: 'btn btn-secondary', onClick: function() { setEditUser(null); } }, 'Cancel'),
682
+ h('button', { className: 'btn btn-primary', onClick: doEditUser }, 'Save Changes')
683
+ )
684
+ },
685
+ h('div', { className: 'form-group' },
686
+ h('label', { className: 'form-label' }, 'Name'),
687
+ h('input', { className: 'input', value: editForm.name, onChange: function(e) { setEditForm(function(f) { return Object.assign({}, f, { name: e.target.value }); }); } })
688
+ ),
689
+ h('div', { className: 'form-group' },
690
+ h('label', { className: 'form-label' }, 'Role'),
691
+ h('select', { className: 'input', value: editForm.role, onChange: function(e) { setEditForm(function(f) { return Object.assign({}, f, { role: e.target.value }); }); } },
692
+ h('option', { value: 'viewer' }, 'Viewer'),
693
+ h('option', { value: 'member' }, 'Member'),
694
+ h('option', { value: 'admin' }, 'Admin'),
695
+ h('option', { value: 'owner' }, 'Owner')
696
+ ),
697
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } },
698
+ editForm.role === 'owner' ? 'Full access to everything. Cannot be restricted.' :
699
+ editForm.role === 'admin' ? 'Full access by default. Can be restricted via permissions.' :
700
+ editForm.role === 'member' ? 'Access controlled by permissions. Can view and act on assigned pages.' :
701
+ 'Read-only access. Can view but not modify.'
702
+ )
703
+ ),
704
+ clientOrgs.length > 0 && h('div', { className: 'form-group' },
705
+ h('label', { className: 'form-label' }, 'Client Organization'),
706
+ h('select', { className: 'input', value: editForm.clientOrgId, onChange: function(e) { setEditForm(function(f) { return Object.assign({}, f, { clientOrgId: e.target.value }); }); } },
707
+ h('option', { value: '' }, 'None (Internal User)'),
708
+ clientOrgs.filter(function(o) { return o.is_active !== false; }).map(function(o) {
709
+ return h('option', { key: o.id, value: o.id }, o.name);
710
+ })
711
+ ),
712
+ editForm.clientOrgId && h('div', { style: { fontSize: 11, color: 'var(--warning, #f59e0b)', marginTop: 4 } },
713
+ 'This user will only see agents and data from this organization.'
714
+ )
715
+ ),
716
+ h('div', { style: { fontSize: 12, color: 'var(--text-muted)', padding: '8px 0', borderTop: '1px solid var(--border)', marginTop: 8 } },
717
+ 'Email: ', h('code', null, editUser.email), ' (cannot be changed)'
718
+ )
719
+ ),
720
+
634
721
  permTarget && pageRegistry && h(PermissionEditor, {
635
722
  userId: permTarget.id,
636
723
  userName: permTarget.name || permTarget.email,
637
724
  currentPerms: permGrants,
638
725
  pageRegistry: pageRegistry,
639
726
  onSave: savePermissions,
640
- onClose: function() { setPermTarget(null); }
727
+ onClose: function() { setPermTarget(null); },
728
+ userObj: permTarget
641
729
  }),
642
730
 
643
731
  // 5-step delete confirmation modal
@@ -743,6 +831,7 @@ export function UsersPage() {
743
831
  h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, u.createdAt ? new Date(u.createdAt).toLocaleDateString() : '-'),
744
832
  h('td', null,
745
833
  h('div', { style: { display: 'flex', gap: 4 } },
834
+ h('button', { className: 'btn btn-ghost btn-sm', title: 'Edit User', onClick: function() { openEditUser(u); } }, I.edit()),
746
835
  h('button', {
747
836
  className: 'btn btn-ghost btn-sm',
748
837
  title: isRestricted ? 'Edit Permissions' : 'Permissions (Owner/Admin have full access)',
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, buildAgentEmailMap, resolveAgentEmail, buildAgentDataMap, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { TimezoneSelect } from '../components/timezones.js';
4
4
  import { HelpButton } from '../components/help-button.js';
@@ -47,7 +47,7 @@ export function WorkforcePage() {
47
47
  setSchedules(schedulesRes.schedules || []);
48
48
  setBudgetData(budgetRes);
49
49
  setClockRecords(recordsRes.records || []);
50
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
50
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(d => setAgents(d.agents || [])).catch(() => {});
51
51
  } catch (err) { toast('Failed to load workforce data', 'error'); }
52
52
  setLoading(false);
53
53
  };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  provision,
3
3
  runSetupWizard
4
- } from "./chunk-4VGAZULN.js";
4
+ } from "./chunk-N5JFOIJN.js";
5
5
  import {
6
6
  AgenticMailManager,
7
7
  GoogleEmailProvider,
@@ -42,7 +42,7 @@ import {
42
42
  requireRole,
43
43
  securityHeaders,
44
44
  validate
45
- } from "./chunk-CRXYUYVJ.js";
45
+ } from "./chunk-4QGERCWK.js";
46
46
  import "./chunk-OF4MUWWS.js";
47
47
  import {
48
48
  PROVIDER_REGISTRY,
@@ -0,0 +1,15 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-4QGERCWK.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-N5JFOIJN.js";
10
+ import "./chunk-QCGSFWKU.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.304",
3
+ "version": "0.5.306",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -216,10 +216,16 @@ export function createAdminRoutes(db: DatabaseAdapter) {
216
216
 
217
217
  api.get('/agents', async (c) => {
218
218
  const status = c.req.query('status') as any;
219
+ const clientOrgId = c.req.query('clientOrgId') || '';
219
220
  const limit = Math.min(parseInt(c.req.query('limit') || '50'), 200);
220
221
  const offset = Math.max(parseInt(c.req.query('offset') || '0'), 0);
221
- const agents = await db.listAgents({ status, limit, offset });
222
- const total = await db.countAgents(status);
222
+ let agents = await db.listAgents({ status, limit, offset });
223
+ let total = await db.countAgents(status);
224
+ // Filter by client org if requested
225
+ if (clientOrgId) {
226
+ agents = agents.filter((a: any) => a.client_org_id === clientOrgId);
227
+ total = agents.length;
228
+ }
223
229
  return c.json({ agents, total, limit, offset });
224
230
  });
225
231
 
@@ -1,4 +1,4 @@
1
- import { h, useState, useEffect, Fragment, useApp, engineCall, showConfirm, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, Fragment, useApp, engineCall, showConfirm, buildAgentEmailMap, buildAgentDataMap, resolveAgentEmail, renderAgentBadge, getOrgId } , apiCall } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { HelpButton } from '../components/help-button.js';
4
4
  import { useOrgContext } from '../components/org-switcher.js';
@@ -16,7 +16,7 @@ export function ApprovalsPage() {
16
16
  const load = () => {
17
17
  engineCall('/approvals/pending').then(d => setPending(d.requests || [])).catch(() => {});
18
18
  engineCall('/approvals/history?limit=50').then(d => setHistory(d.requests || [])).catch(() => {});
19
- engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
19
+ apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).then(d => setAgents(d.agents || [])).catch(() => {});
20
20
  };
21
21
  useEffect(() => { load(); }, []);
22
22