@agenticmail/enterprise 0.5.305 → 0.5.307
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4QGERCWK.js +4400 -0
- package/dist/chunk-N5JFOIJN.js +1519 -0
- package/dist/cli-serve-KZT4WNKR.js +143 -0
- package/dist/cli.js +2 -2
- package/dist/dashboard/app.js +7 -5
- package/dist/dashboard/components/org-switcher.js +1 -6
- package/dist/dashboard/pages/approvals.js +2 -2
- package/dist/dashboard/pages/dashboard.js +6 -5
- package/dist/dashboard/pages/guardrails.js +2 -2
- package/dist/dashboard/pages/journal.js +2 -2
- package/dist/dashboard/pages/knowledge-contributions.js +1 -1
- package/dist/dashboard/pages/knowledge.js +14 -5
- package/dist/dashboard/pages/messages.js +2 -2
- package/dist/dashboard/pages/org-chart.js +2 -2
- package/dist/dashboard/pages/task-pipeline.js +17 -7
- package/dist/dashboard/pages/users.js +64 -0
- package/dist/dashboard/pages/workforce.js +2 -2
- package/dist/index.js +2 -2
- package/dist/server-43HP7BAB.js +15 -0
- package/dist/setup-2NQBX5FF.js +20 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +8 -2
- package/src/dashboard/app.js +7 -5
- package/src/dashboard/components/org-switcher.js +1 -6
- package/src/dashboard/pages/approvals.js +2 -2
- package/src/dashboard/pages/dashboard.js +6 -5
- package/src/dashboard/pages/guardrails.js +2 -2
- package/src/dashboard/pages/journal.js +2 -2
- package/src/dashboard/pages/knowledge-contributions.js +1 -1
- package/src/dashboard/pages/knowledge.js +14 -5
- package/src/dashboard/pages/messages.js +2 -2
- package/src/dashboard/pages/org-chart.js +2 -2
- package/src/dashboard/pages/task-pipeline.js +17 -7
- package/src/dashboard/pages/users.js +64 -0
- package/src/dashboard/pages/workforce.js +2 -2
|
@@ -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-
|
|
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-
|
|
67
|
+
import("./setup-2NQBX5FF.js").then((m) => m.runSetupWizard()).catch(fatal);
|
|
68
68
|
break;
|
|
69
69
|
}
|
|
70
70
|
function fatal(err) {
|
package/dist/dashboard/app.js
CHANGED
|
@@ -313,17 +313,19 @@ function App() {
|
|
|
313
313
|
}, []);
|
|
314
314
|
|
|
315
315
|
const stopImpersonation = useCallback(() => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
setImpersonating(prev => {
|
|
317
|
+
if (prev && prev.originalToken) {
|
|
318
|
+
localStorage.setItem('em_token', prev.originalToken);
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
});
|
|
320
322
|
localStorage.removeItem('em_client_org_id');
|
|
321
323
|
// Reload real user
|
|
322
324
|
authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {});
|
|
323
325
|
apiCall('/me/permissions').then(d => { if (d && d.permissions) setPermissions(d.permissions); }).catch(() => {});
|
|
324
326
|
toast('Stopped impersonation', 'success');
|
|
325
327
|
setPage('users');
|
|
326
|
-
}, [
|
|
328
|
+
}, []);
|
|
327
329
|
|
|
328
330
|
return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions, impersonating, startImpersonation, stopImpersonation } },
|
|
329
331
|
h('div', { className: 'app-layout' },
|
|
@@ -28,13 +28,8 @@ export function OrgContextSwitcher(props) {
|
|
|
28
28
|
var list = d.organizations || [];
|
|
29
29
|
setOrgs(list);
|
|
30
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
31
|
}).catch(function() { setLoaded(true); });
|
|
37
|
-
}, [
|
|
32
|
+
}, []);
|
|
38
33
|
|
|
39
34
|
// Don't render if no client orgs and user isn't org-bound
|
|
40
35
|
if (loaded && orgs.length === 0 && !userOrgId) return null;
|
|
@@ -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
|
-
|
|
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
|
|
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(
|
|
64
|
-
engineCall('/agents?orgId=' +
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
480
|
+
apiCall('/agents' + (orgCtx.selectedOrgId ? '?clientOrgId=' + orgCtx.selectedOrgId : '')).catch(function() { return { agents: [] }; }),
|
|
481
481
|
]).then(function(res) {
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
var allTasks = res[0]?.tasks || [];
|
|
483
|
+
var orgAgents = res[2]?.agents || [];
|
|
484
484
|
// Build agent avatar/name map
|
|
485
485
|
var map = {};
|
|
486
|
-
|
|
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]);
|
|
@@ -494,6 +494,21 @@ export function UsersPage() {
|
|
|
494
494
|
setResetting(false);
|
|
495
495
|
};
|
|
496
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
|
+
|
|
497
512
|
var toggleActive = async function(user) {
|
|
498
513
|
var action = user.isActive === false ? 'reactivate' : 'deactivate';
|
|
499
514
|
var ok = await showConfirm({
|
|
@@ -515,6 +530,8 @@ export function UsersPage() {
|
|
|
515
530
|
var [deleteStep, setDeleteStep] = useState(0);
|
|
516
531
|
var [deleteTarget, setDeleteTarget] = useState(null);
|
|
517
532
|
var [deleteTyped, setDeleteTyped] = useState('');
|
|
533
|
+
var [editUser, setEditUser] = useState(null);
|
|
534
|
+
var [editForm, setEditForm] = useState({ name: '', role: '', clientOrgId: '' });
|
|
518
535
|
|
|
519
536
|
var startDelete = function(user) { setDeleteTarget(user); setDeleteStep(1); setDeleteTyped(''); };
|
|
520
537
|
var cancelDelete = function() { setDeleteTarget(null); setDeleteStep(0); setDeleteTyped(''); };
|
|
@@ -655,6 +672,52 @@ export function UsersPage() {
|
|
|
655
672
|
),
|
|
656
673
|
|
|
657
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
|
+
|
|
658
721
|
permTarget && pageRegistry && h(PermissionEditor, {
|
|
659
722
|
userId: permTarget.id,
|
|
660
723
|
userName: permTarget.name || permTarget.email,
|
|
@@ -768,6 +831,7 @@ export function UsersPage() {
|
|
|
768
831
|
h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, u.createdAt ? new Date(u.createdAt).toLocaleDateString() : '-'),
|
|
769
832
|
h('td', null,
|
|
770
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()),
|
|
771
835
|
h('button', {
|
|
772
836
|
className: 'btn btn-ghost btn-sm',
|
|
773
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
|
-
|
|
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-
|
|
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-
|
|
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
package/src/admin/routes.ts
CHANGED
|
@@ -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
|
-
|
|
222
|
-
|
|
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
|
|
package/src/dashboard/app.js
CHANGED
|
@@ -313,17 +313,19 @@ function App() {
|
|
|
313
313
|
}, []);
|
|
314
314
|
|
|
315
315
|
const stopImpersonation = useCallback(() => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
316
|
+
setImpersonating(prev => {
|
|
317
|
+
if (prev && prev.originalToken) {
|
|
318
|
+
localStorage.setItem('em_token', prev.originalToken);
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
});
|
|
320
322
|
localStorage.removeItem('em_client_org_id');
|
|
321
323
|
// Reload real user
|
|
322
324
|
authCall('/me').then(d => { setUser(d.user || d); }).catch(() => {});
|
|
323
325
|
apiCall('/me/permissions').then(d => { if (d && d.permissions) setPermissions(d.permissions); }).catch(() => {});
|
|
324
326
|
toast('Stopped impersonation', 'success');
|
|
325
327
|
setPage('users');
|
|
326
|
-
}, [
|
|
328
|
+
}, []);
|
|
327
329
|
|
|
328
330
|
return h(AppContext.Provider, { value: { toast, toasts, user, theme, setPage, permissions, impersonating, startImpersonation, stopImpersonation } },
|
|
329
331
|
h('div', { className: 'app-layout' },
|