@agenticmail/enterprise 0.5.300 → 0.5.302

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 (46) hide show
  1. package/dist/chunk-QKQWDX6M.js +1519 -0
  2. package/dist/chunk-VLVRDLYO.js +48 -0
  3. package/dist/chunk-VMVOJFCX.js +4338 -0
  4. package/dist/cli-agent-4GZGC6XO.js +1778 -0
  5. package/dist/cli-recover-WS27YEB7.js +487 -0
  6. package/dist/cli-serve-HVULKNQH.js +143 -0
  7. package/dist/cli-verify-CVYMUGKX.js +149 -0
  8. package/dist/cli.js +5 -5
  9. package/dist/dashboard/components/org-switcher.js +96 -0
  10. package/dist/dashboard/pages/agents.js +8 -1
  11. package/dist/dashboard/pages/approvals.js +5 -1
  12. package/dist/dashboard/pages/dashboard.js +6 -2
  13. package/dist/dashboard/pages/guardrails.js +20 -16
  14. package/dist/dashboard/pages/journal.js +6 -2
  15. package/dist/dashboard/pages/knowledge-contributions.js +18 -10
  16. package/dist/dashboard/pages/knowledge.js +32 -9
  17. package/dist/dashboard/pages/messages.js +8 -4
  18. package/dist/dashboard/pages/org-chart.js +5 -1
  19. package/dist/dashboard/pages/organizations.js +166 -13
  20. package/dist/dashboard/pages/skills.js +15 -11
  21. package/dist/dashboard/pages/task-pipeline.js +6 -2
  22. package/dist/dashboard/pages/workforce.js +5 -1
  23. package/dist/factory-XEBV2VGZ.js +9 -0
  24. package/dist/index.js +3 -3
  25. package/dist/postgres-NZBDKOQR.js +816 -0
  26. package/dist/server-3HZEV5X2.js +15 -0
  27. package/dist/setup-JUB67BUU.js +20 -0
  28. package/dist/sqlite-INPN4DQN.js +545 -0
  29. package/package.json +1 -1
  30. package/src/admin/routes.ts +94 -5
  31. package/src/dashboard/components/org-switcher.js +96 -0
  32. package/src/dashboard/pages/agents.js +8 -1
  33. package/src/dashboard/pages/approvals.js +5 -1
  34. package/src/dashboard/pages/dashboard.js +6 -2
  35. package/src/dashboard/pages/guardrails.js +20 -16
  36. package/src/dashboard/pages/journal.js +6 -2
  37. package/src/dashboard/pages/knowledge-contributions.js +18 -10
  38. package/src/dashboard/pages/knowledge.js +32 -9
  39. package/src/dashboard/pages/messages.js +8 -4
  40. package/src/dashboard/pages/org-chart.js +5 -1
  41. package/src/dashboard/pages/organizations.js +166 -13
  42. package/src/dashboard/pages/skills.js +15 -11
  43. package/src/dashboard/pages/task-pipeline.js +6 -2
  44. package/src/dashboard/pages/workforce.js +5 -1
  45. package/src/db/postgres.ts +17 -0
  46. package/src/db/sqlite.ts +8 -0
@@ -1,14 +1,17 @@
1
- import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, getOrgId } from '../components/utils.js';
1
+ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, apiCall, getOrgId } from '../components/utils.js';
2
2
  import { I } from '../components/icons.js';
3
3
  import { Modal } from '../components/modal.js';
4
4
  import { KnowledgeImportWizard, ImportJobsList } from './knowledge-import.js';
5
5
  import { HelpButton } from '../components/help-button.js';
6
+ import { useOrgContext } from '../components/org-switcher.js';
6
7
 
7
8
  export function KnowledgeBasePage() {
8
9
  const { toast } = useApp();
9
10
  const [kbs, setKbs] = useState([]);
10
11
  const [creating, setCreating] = useState(false);
11
- const [form, setForm] = useState({ name: '', description: '' });
12
+ const [form, setForm] = useState({ name: '', description: '', orgId: '' });
13
+ const [clientOrgs, setClientOrgs] = useState([]);
14
+ const orgCtx = useOrgContext();
12
15
  const [selected, setSelected] = useState(null); // full KB detail
13
16
  const [docs, setDocs] = useState([]);
14
17
  const [chunks, setChunks] = useState([]);
@@ -25,14 +28,20 @@ export function KnowledgeBasePage() {
25
28
 
26
29
  const load = useCallback(() => {
27
30
  engineCall('/knowledge-bases').then(d => setKbs(d.knowledgeBases || [])).catch(() => {});
31
+ apiCall('/organizations').then(d => setClientOrgs(d.organizations || [])).catch(() => {});
28
32
  }, []);
29
33
  useEffect(() => { load(); }, [load]);
30
34
 
35
+ // Filter KBs by selected org context
36
+ const filteredKbs = orgCtx.selectedOrgId
37
+ ? kbs.filter(kb => kb.orgId === orgCtx.selectedOrgId || kb.clientOrgId === orgCtx.selectedOrgId)
38
+ : kbs;
39
+
31
40
  const create = async () => {
32
41
  try {
33
- await engineCall('/knowledge-bases', { method: 'POST', body: JSON.stringify({ name: form.name, description: form.description, orgId: getOrgId() }) });
42
+ await engineCall('/knowledge-bases', { method: 'POST', body: JSON.stringify({ name: form.name, description: form.description, orgId: getOrgId(), clientOrgId: form.orgId || null }) });
34
43
  toast('Knowledge base created', 'success');
35
- setCreating(false); setForm({ name: '', description: '' }); load();
44
+ setCreating(false); setForm({ name: '', description: '', orgId: '' }); load();
36
45
  } catch (e) { toast(e.message, 'error'); }
37
46
  };
38
47
 
@@ -315,16 +324,29 @@ export function KnowledgeBasePage() {
315
324
  h('button', { className: 'btn btn-primary', onClick: () => setCreating(true) }, I.plus(), ' New Knowledge Base')
316
325
  ),
317
326
 
327
+ // Org context switcher
328
+ h(orgCtx.Switcher),
329
+
318
330
  creating && h(Modal, { title: 'Create Knowledge Base', onClose: () => setCreating(false), footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: () => setCreating(false) }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.name }, 'Create')) },
319
331
  h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: e => setForm(f => ({ ...f, name: e.target.value })) })),
320
- h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Description'), h('textarea', { className: 'input', value: form.description, onChange: e => setForm(f => ({ ...f, description: e.target.value })) }))
332
+ h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Description'), h('textarea', { className: 'input', value: form.description, onChange: e => setForm(f => ({ ...f, description: e.target.value })) })),
333
+ clientOrgs.length > 0 && h('div', { className: 'form-group' },
334
+ h('label', { className: 'form-label' }, 'Organization'),
335
+ h('select', { className: 'input', value: form.orgId, onChange: e => setForm(f => ({ ...f, orgId: e.target.value })) },
336
+ h('option', { value: '' }, 'My Organization (internal)'),
337
+ clientOrgs.filter(o => o.is_active !== false).map(o =>
338
+ h('option', { key: o.id, value: o.id }, o.name)
339
+ )
340
+ ),
341
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)', marginTop: 4 } }, 'Assign this knowledge base to a client organization for data isolation')
342
+ )
321
343
  ),
322
344
 
323
345
  loading && h('div', { style: { textAlign: 'center', padding: 40 } }, 'Loading...'),
324
346
 
325
- !loading && kbs.length === 0
326
- ? h('div', { className: 'card' }, h('div', { className: 'card-body' }, h('div', { className: 'empty-state' }, I.knowledge(), h('h3', null, 'No knowledge bases'), h('p', null, 'Create a knowledge base to give agents access to your documents, policies, and data.'))))
327
- : !loading && h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 } }, kbs.map(kb =>
347
+ !loading && filteredKbs.length === 0
348
+ ? h('div', { className: 'card' }, h('div', { className: 'card-body' }, h('div', { className: 'empty-state' }, I.knowledge(), h('h3', null, orgCtx.selectedOrgId ? 'No knowledge bases for this organization' : 'No knowledge bases'), h('p', null, orgCtx.selectedOrgId ? 'Create a knowledge base assigned to this organization to get started.' : 'Create a knowledge base to give agents access to your documents, policies, and data.'))))
349
+ : !loading && h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 16 } }, filteredKbs.map(kb =>
328
350
  h('div', { key: kb.id, className: 'card', style: { cursor: 'pointer', transition: 'border-color 0.15s' }, onClick: () => selectKb(kb) },
329
351
  h('div', { className: 'card-body' },
330
352
  h('h3', { style: { fontSize: 15, fontWeight: 600, marginBottom: 4 } }, kb.name),
@@ -332,7 +354,8 @@ export function KnowledgeBasePage() {
332
354
  h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } },
333
355
  h('span', { className: 'badge badge-info' }, (kb.stats?.documentCount || kb.stats?.documents || kb.stats?.totalDocuments || kb.documents?.length || 0) + ' docs'),
334
356
  h('span', { className: 'badge badge-neutral' }, (kb.stats?.chunkCount || kb.stats?.chunks || kb.stats?.totalChunks || 0) + ' chunks'),
335
- kb.agentIds && kb.agentIds.length > 0 && h('span', { className: 'badge badge-success' }, kb.agentIds.length + ' agent(s)')
357
+ kb.agentIds && kb.agentIds.length > 0 && h('span', { className: 'badge badge-success' }, kb.agentIds.length + ' agent(s)'),
358
+ (function() { var org = kb.clientOrgId && clientOrgs.find(function(o) { return o.id === kb.clientOrgId; }); return org ? h('span', { className: 'badge', style: { background: 'var(--bg-secondary)', fontSize: 10 } }, I.building(), ' ', org.name) : null; })()
336
359
  ),
337
360
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 10 } },
338
361
  h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Click to view details \u2192'),
@@ -1,8 +1,11 @@
1
1
  import { h, useState, useEffect, useRef, 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
  export function MessagesPage() {
7
+ var orgCtx = useOrgContext();
8
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
6
9
  const { toast } = useApp();
7
10
  const [messages, setMessages] = useState([]);
8
11
  const [agents, setAgents] = useState([]);
@@ -10,19 +13,19 @@ export function MessagesPage() {
10
13
  const [mainTab, setMainTab] = useState('messages');
11
14
  const [subTab, setSubTab] = useState('all');
12
15
  const [showModal, setShowModal] = useState(false);
13
- const [form, setForm] = useState({ orgId: getOrgId(), fromAgentId: '', toAgentId: '', subject: '', content: '', priority: 'normal' });
16
+ const [form, setForm] = useState({ orgId: effectiveOrgId, fromAgentId: '', toAgentId: '', subject: '', content: '', priority: 'normal' });
14
17
  const [selectedNode, setSelectedNode] = useState(null);
15
18
  const [nodePositions, setNodePositions] = useState([]);
16
19
  const svgRef = useRef(null);
17
20
 
18
21
  const loadMessages = () => {
19
- engineCall('/messages?orgId=' + getOrgId() + '&limit=100').then(d => setMessages(d.messages || [])).catch(() => {});
22
+ engineCall('/messages?orgId=' + effectiveOrgId + '&limit=100').then(d => setMessages(d.messages || [])).catch(() => {});
20
23
  };
21
24
  const loadAgents = () => {
22
- engineCall('/agents?orgId=' + getOrgId()).then(d => setAgents(d.agents || [])).catch(() => {});
25
+ engineCall('/agents?orgId=' + effectiveOrgId).then(d => setAgents(d.agents || [])).catch(() => {});
23
26
  };
24
27
  const loadTopology = () => {
25
- engineCall('/messages/topology?orgId=' + getOrgId()).then(d => setTopology(d.topology || null)).catch(() => {});
28
+ engineCall('/messages/topology?orgId=' + effectiveOrgId).then(d => setTopology(d.topology || null)).catch(() => {});
26
29
  };
27
30
  useEffect(() => { loadMessages(); loadAgents(); loadTopology(); }, []);
28
31
 
@@ -200,6 +203,7 @@ export function MessagesPage() {
200
203
  var _tip = { marginTop: 12, padding: 12, background: 'var(--bg-secondary, #1e293b)', borderRadius: 'var(--radius, 8px)', fontSize: 13 };
201
204
 
202
205
  return h('div', { className: 'page-inner' },
206
+ h(orgCtx.Switcher),
203
207
  // Page header
204
208
  h('div', { className: 'page-header' }, h('h1', { style: { display: 'flex', alignItems: 'center' } }, 'Agent Messages', h(HelpButton, { label: 'Agent Messages' },
205
209
  h('p', null, 'All inter-agent and external communications in one place. See how your agents talk to each other and to the outside world.'),
@@ -1,6 +1,7 @@
1
1
  import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, 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
  // ─── Inject theme CSS once ───────────────────────────────
6
7
  var _injected = false;
@@ -133,6 +134,8 @@ function OrgSummary(props) {
133
134
 
134
135
  // ─── Main Component ─────────────────────────────────────
135
136
  export function OrgChartPage() {
137
+ var orgCtx = useOrgContext();
138
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
136
139
  injectCSS();
137
140
  var app = useApp();
138
141
  var toast = app.toast;
@@ -151,7 +154,7 @@ export function OrgChartPage() {
151
154
  setLoading(true); setError(null);
152
155
  Promise.all([
153
156
  engineCall('/hierarchy/org-chart').catch(function() { return null; }),
154
- engineCall('/agents?orgId=' + getOrgId()).catch(function() { return { agents: [] }; }),
157
+ engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
155
158
  ]).then(function(res) {
156
159
  var hierRes = res[0]; var agentRes = res[1];
157
160
  var avatarMap = {};
@@ -211,6 +214,7 @@ export function OrgChartPage() {
211
214
  );
212
215
 
213
216
  return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--oc-bg)', borderRadius: 'var(--radius-lg)', overflow: 'hidden' } },
217
+ h(orgCtx.Switcher, { style: { margin: '8px 12px 0', borderRadius: 6 } }),
214
218
  // Toolbar
215
219
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderBottom: '1px solid var(--oc-border)', background: 'var(--oc-toolbar)', flexShrink: 0, flexWrap: 'wrap' } },
216
220
  h('div', { style: { fontWeight: 700, fontSize: 14, color: 'var(--oc-text)', display: 'flex', alignItems: 'center', gap: 6 } },
@@ -41,8 +41,18 @@ export function OrganizationsPage() {
41
41
  var femail = _femail[0]; var setFemail = _femail[1];
42
42
  var _fdesc = useState('');
43
43
  var fdesc = _fdesc[0]; var setFdesc = _fdesc[1];
44
+ var _fbilling = useState('');
45
+ var fbilling = _fbilling[0]; var setFbilling = _fbilling[1];
46
+ var _fcurrency = useState('USD');
47
+ var fcurrency = _fcurrency[0]; var setFcurrency = _fcurrency[1];
44
48
  var _slugManual = useState(false);
45
49
  var slugManual = _slugManual[0]; var setSlugManual = _slugManual[1];
50
+ var _detailTab = useState('agents');
51
+ var detailTab = _detailTab[0]; var setDetailTab = _detailTab[1];
52
+ var _billingSummary = useState([]);
53
+ var billingSummary = _billingSummary[0]; var setBillingSummary = _billingSummary[1];
54
+ var _billingRecords = useState([]);
55
+ var billingRecords = _billingRecords[0]; var setBillingRecords = _billingRecords[1];
46
56
 
47
57
  var loadOrgs = useCallback(function() {
48
58
  setLoading(true);
@@ -62,29 +72,33 @@ export function OrganizationsPage() {
62
72
  };
63
73
 
64
74
  var openCreate = function() {
65
- setFname(''); setFslug(''); setFcontact(''); setFemail(''); setFdesc(''); setSlugManual(false);
75
+ setFname(''); setFslug(''); setFcontact(''); setFemail(''); setFdesc(''); setFbilling(''); setFcurrency('USD'); setSlugManual(false);
66
76
  setShowCreate(true);
67
77
  };
68
78
 
69
79
  var openEdit = function(org) {
70
80
  setFname(org.name || ''); setFslug(org.slug || ''); setFcontact(org.contact_name || ''); setFemail(org.contact_email || ''); setFdesc(org.description || '');
81
+ setFbilling(org.billing_rate_per_agent ? String(org.billing_rate_per_agent) : ''); setFcurrency(org.currency || 'USD');
71
82
  setEditOrg(org);
72
83
  };
73
84
 
74
85
  var openDetail = function(org) {
75
86
  setDetailOrg(org);
87
+ setDetailTab('agents');
76
88
  loadAllAgents();
77
89
  apiCall('/organizations/' + org.id).then(function(data) {
78
90
  setDetailAgents(data.agents || []);
79
91
  setDetailOrg(data);
80
92
  }).catch(function(err) { toast(err.message, 'error'); });
93
+ apiCall('/organizations/' + org.id + '/billing-summary').then(function(d) { setBillingSummary(d.summary || []); }).catch(function() {});
94
+ apiCall('/organizations/' + org.id + '/billing').then(function(d) { setBillingRecords(d.records || []); }).catch(function() {});
81
95
  };
82
96
 
83
97
  var doCreate = function() {
84
98
  setActing('create');
85
99
  apiCall('/organizations', {
86
100
  method: 'POST',
87
- body: JSON.stringify({ name: fname, slug: fslug, contact_name: fcontact, contact_email: femail, description: fdesc })
101
+ body: JSON.stringify({ name: fname, slug: fslug, contact_name: fcontact, contact_email: femail, description: fdesc, billing_rate_per_agent: fbilling ? parseFloat(fbilling) : 0, currency: fcurrency })
88
102
  }).then(function() {
89
103
  toast('Organization created', 'success');
90
104
  setShowCreate(false);
@@ -97,7 +111,7 @@ export function OrganizationsPage() {
97
111
  setActing('edit');
98
112
  apiCall('/organizations/' + editOrg.id, {
99
113
  method: 'PATCH',
100
- body: JSON.stringify({ name: fname, contact_name: fcontact, contact_email: femail, description: fdesc })
114
+ body: JSON.stringify({ name: fname, contact_name: fcontact, contact_email: femail, description: fdesc, billing_rate_per_agent: fbilling ? parseFloat(fbilling) : 0, currency: fcurrency })
101
115
  }).then(function() {
102
116
  toast('Organization updated', 'success');
103
117
  setEditOrg(null);
@@ -209,8 +223,8 @@ export function OrganizationsPage() {
209
223
  org.description && h('div', { style: { fontSize: 13, color: 'var(--text-secondary)', marginBottom: 12, lineHeight: 1.5 } }, org.description),
210
224
  h('div', { style: { display: 'flex', gap: 16, fontSize: 12, color: 'var(--text-muted)' } },
211
225
  h('span', null, I.agents(), ' ', (org.agent_count || 0), ' agent', (org.agent_count || 0) !== 1 ? 's' : ''),
212
- org.contact_email && h('span', null, I.mail(), ' ', org.contact_email),
213
- org.created_at && h('span', null, new Date(org.created_at).toLocaleDateString())
226
+ org.billing_rate_per_agent > 0 && h('span', { style: { fontWeight: 600, color: 'var(--success, #15803d)' } }, (org.currency || '$') + ' ', parseFloat(org.billing_rate_per_agent).toFixed(2), '/agent/mo'),
227
+ org.contact_email && h('span', null, I.mail(), ' ', org.contact_email)
214
228
  ),
215
229
  h('div', { style: { display: 'flex', gap: 6, marginTop: 12, borderTop: '1px solid var(--border)', paddingTop: 10 }, onClick: function(e) { e.stopPropagation(); } },
216
230
  h('button', { className: 'btn btn-ghost btn-sm', onClick: function() { openEdit(org); } }, I.edit(), ' Edit'),
@@ -229,24 +243,36 @@ export function OrganizationsPage() {
229
243
  h('div', { style: { display: 'flex', flexDirection: 'column', gap: 14, padding: 4 } },
230
244
  h('div', null,
231
245
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Name *'),
232
- h('input', { className: 'input', value: fname, onInput: function(e) { setFname(e.target.value); if (!slugManual) setFslug(slugify(e.target.value)); }, placeholder: 'Acme Corporation' })
246
+ h('input', { className: 'input', value: fname, onInput: function(e) { setFname(e.target.value); if (!slugManual) setFslug(slugify(e.target.value)); }, placeholder: 'AgenticMail' })
233
247
  ),
234
248
  h('div', null,
235
249
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Slug *'),
236
- h('input', { className: 'input', value: fslug, onInput: function(e) { setFslug(e.target.value); setSlugManual(true); }, placeholder: 'acme-corporation', style: { fontFamily: 'var(--font-mono, monospace)' } })
250
+ h('input', { className: 'input', value: fslug, onInput: function(e) { setFslug(e.target.value); setSlugManual(true); }, placeholder: 'agenticmail', style: { fontFamily: 'var(--font-mono, monospace)' } })
237
251
  ),
238
252
  h('div', null,
239
253
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Name'),
240
- h('input', { className: 'input', value: fcontact, onInput: function(e) { setFcontact(e.target.value); }, placeholder: 'John Doe' })
254
+ h('input', { className: 'input', value: fcontact, onInput: function(e) { setFcontact(e.target.value); }, placeholder: 'Ope Olatunji' })
241
255
  ),
242
256
  h('div', null,
243
257
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Contact Email'),
244
- h('input', { className: 'input', type: 'email', value: femail, onInput: function(e) { setFemail(e.target.value); }, placeholder: 'john@acme.com' })
258
+ h('input', { className: 'input', type: 'email', value: femail, onInput: function(e) { setFemail(e.target.value); }, placeholder: 'ope@agenticmail.io' })
245
259
  ),
246
260
  h('div', null,
247
261
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Description'),
248
262
  h('textarea', { className: 'input', value: fdesc, onInput: function(e) { setFdesc(e.target.value); }, placeholder: 'Brief description...', rows: 3, style: { resize: 'vertical' } })
249
263
  ),
264
+ h('div', { style: { display: 'flex', gap: 12 } },
265
+ h('div', { style: { flex: 1 } },
266
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Billing Rate / Agent / Month'),
267
+ h('input', { className: 'input', type: 'number', step: '0.01', min: '0', value: fbilling, onInput: function(e) { setFbilling(e.target.value); }, placeholder: '0.00' })
268
+ ),
269
+ h('div', { style: { width: 100 } },
270
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Currency'),
271
+ h('select', { className: 'input', value: fcurrency, onChange: function(e) { setFcurrency(e.target.value); } },
272
+ h('option', { value: 'USD' }, 'USD'), h('option', { value: 'EUR' }, 'EUR'), h('option', { value: 'GBP' }, 'GBP'), h('option', { value: 'NGN' }, 'NGN'), h('option', { value: 'CAD' }, 'CAD'), h('option', { value: 'AUD' }, 'AUD')
273
+ )
274
+ )
275
+ ),
250
276
  h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 } },
251
277
  h('button', { className: 'btn btn-secondary', onClick: function() { setShowCreate(false); } }, 'Cancel'),
252
278
  h('button', { className: 'btn btn-primary', disabled: !fname || !fslug || acting === 'create', onClick: doCreate }, acting === 'create' ? 'Creating...' : 'Create')
@@ -277,6 +303,18 @@ export function OrganizationsPage() {
277
303
  h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Description'),
278
304
  h('textarea', { className: 'input', value: fdesc, onInput: function(e) { setFdesc(e.target.value); }, rows: 3, style: { resize: 'vertical' } })
279
305
  ),
306
+ h('div', { style: { display: 'flex', gap: 12 } },
307
+ h('div', { style: { flex: 1 } },
308
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Billing Rate / Agent / Month'),
309
+ h('input', { className: 'input', type: 'number', step: '0.01', min: '0', value: fbilling, onInput: function(e) { setFbilling(e.target.value); }, placeholder: '0.00' })
310
+ ),
311
+ h('div', { style: { width: 100 } },
312
+ h('label', { style: { fontSize: 12, fontWeight: 600, display: 'block', marginBottom: 4 } }, 'Currency'),
313
+ h('select', { className: 'input', value: fcurrency, onChange: function(e) { setFcurrency(e.target.value); } },
314
+ h('option', { value: 'USD' }, 'USD'), h('option', { value: 'EUR' }, 'EUR'), h('option', { value: 'GBP' }, 'GBP'), h('option', { value: 'NGN' }, 'NGN'), h('option', { value: 'CAD' }, 'CAD'), h('option', { value: 'AUD' }, 'AUD')
315
+ )
316
+ )
317
+ ),
280
318
  h('div', { style: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 } },
281
319
  h('button', { className: 'btn btn-secondary', onClick: function() { setEditOrg(null); } }, 'Cancel'),
282
320
  h('button', { className: 'btn btn-primary', disabled: !fname || acting === 'edit', onClick: doEdit }, acting === 'edit' ? 'Saving...' : 'Save Changes')
@@ -285,10 +323,10 @@ export function OrganizationsPage() {
285
323
  ),
286
324
 
287
325
  // Detail Modal
288
- detailOrg && h(Modal, { title: detailOrg.name || 'Organization Detail', onClose: function() { setDetailOrg(null); }, wide: true },
326
+ detailOrg && h(Modal, { title: detailOrg.name || 'Organization Detail', onClose: function() { setDetailOrg(null); }, width: 700 },
289
327
  h('div', { style: { padding: 4 } },
290
328
  // Org info
291
- h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 16, marginBottom: 20 } },
329
+ h('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, marginBottom: 16 } },
292
330
  h('div', null,
293
331
  h('div', { style: { fontSize: 11, color: 'var(--text-muted)', textTransform: 'uppercase', fontWeight: 600, marginBottom: 4 } }, 'Slug'),
294
332
  h('div', { style: { fontFamily: 'var(--font-mono, monospace)', fontSize: 13 } }, detailOrg.slug)
@@ -303,10 +341,29 @@ export function OrganizationsPage() {
303
341
  detailOrg.contact_email && h('div', { style: { fontSize: 12, color: 'var(--text-muted)' } }, detailOrg.contact_email)
304
342
  )
305
343
  ),
344
+ // Billing rate in header
345
+ detailOrg.billing_rate_per_agent > 0 && h('div', { style: { display: 'flex', alignItems: 'center', gap: 16, padding: '10px 14px', background: 'var(--success-soft, rgba(21,128,61,0.06))', borderRadius: 8, marginBottom: 16, fontSize: 13 } },
346
+ h('div', null, h('strong', null, 'Rate: '), (detailOrg.currency || 'USD') + ' ' + parseFloat(detailOrg.billing_rate_per_agent).toFixed(2) + '/agent/month'),
347
+ h('div', null, h('strong', null, 'Monthly Revenue: '), (detailOrg.currency || 'USD') + ' ' + (parseFloat(detailOrg.billing_rate_per_agent) * detailAgents.length).toFixed(2)),
348
+ h('div', null, h('strong', null, 'Agents: '), detailAgents.length)
349
+ ),
350
+
306
351
  detailOrg.description && h('div', { style: { fontSize: 13, color: 'var(--text-secondary)', marginBottom: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 'var(--radius)' } }, detailOrg.description),
307
352
 
308
- // Linked agents
309
- h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 10 } }, 'Linked Agents (' + detailAgents.length + ')'),
353
+ // Tabs
354
+ h('div', { style: { display: 'flex', gap: 0, borderBottom: '1px solid var(--border)', marginBottom: 16 } },
355
+ ['agents', 'billing'].map(function(t) {
356
+ return h('button', {
357
+ key: t, type: 'button',
358
+ style: { padding: '8px 16px', fontSize: 13, fontWeight: 600, background: 'none', border: 'none', cursor: 'pointer', color: detailTab === t ? 'var(--primary)' : 'var(--text-muted)', borderBottom: detailTab === t ? '2px solid var(--primary)' : '2px solid transparent', fontFamily: 'var(--font)' },
359
+ onClick: function() { setDetailTab(t); }
360
+ }, t === 'agents' ? 'Agents (' + detailAgents.length + ')' : 'Billing & Costs');
361
+ })
362
+ ),
363
+
364
+ // ── Agents Tab ────────────────────────────
365
+ detailTab === 'agents' && h(Fragment, null,
366
+ h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 10 } }, 'Linked Agents'),
310
367
  detailAgents.length > 0
311
368
  ? h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 16 } },
312
369
  detailAgents.map(function(a) {
@@ -339,6 +396,102 @@ export function OrganizationsPage() {
339
396
  ),
340
397
  h('button', { className: 'btn btn-primary btn-sm', disabled: !assignAgentId || acting === 'assign', onClick: doAssignAgent }, acting === 'assign' ? 'Assigning...' : 'Assign')
341
398
  )
399
+ ), // end agents tab
400
+
401
+ // ── Billing Tab ───────────────────────────
402
+ detailTab === 'billing' && h(Fragment, null,
403
+ // Revenue vs Cost chart
404
+ billingSummary.length > 0 && h('div', { style: { marginBottom: 20 } },
405
+ h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 12 } }, 'Revenue vs Cost'),
406
+ h('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 4, height: 160, padding: '0 8px', borderBottom: '1px solid var(--border)' } },
407
+ billingSummary.map(function(m, i) {
408
+ var rev = parseFloat(m.total_revenue) || 0;
409
+ var cost = parseFloat(m.total_cost) || 0;
410
+ var maxVal = Math.max.apply(null, billingSummary.map(function(s) { return Math.max(parseFloat(s.total_revenue) || 0, parseFloat(s.total_cost) || 0); })) || 1;
411
+ var revH = Math.max(4, (rev / maxVal) * 140);
412
+ var costH = Math.max(4, (cost / maxVal) * 140);
413
+ var profit = rev - cost;
414
+ return h('div', { key: i, style: { flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 } },
415
+ h('div', { style: { display: 'flex', alignItems: 'flex-end', gap: 2, height: 144 } },
416
+ h('div', { title: 'Revenue: ' + rev.toFixed(2), style: { width: 14, height: revH, background: 'var(--success, #15803d)', borderRadius: '3px 3px 0 0', minHeight: 4 } }),
417
+ h('div', { title: 'Cost: ' + cost.toFixed(2), style: { width: 14, height: costH, background: 'var(--danger, #dc2626)', borderRadius: '3px 3px 0 0', minHeight: 4, opacity: 0.7 } })
418
+ ),
419
+ h('div', { style: { fontSize: 9, color: 'var(--text-muted)', marginTop: 4, whiteSpace: 'nowrap' } }, m.month ? m.month.slice(5) : ''),
420
+ h('div', { style: { fontSize: 9, color: profit >= 0 ? 'var(--success, #15803d)' : 'var(--danger)', fontWeight: 600 } }, profit >= 0 ? '+' + profit.toFixed(0) : profit.toFixed(0))
421
+ );
422
+ })
423
+ ),
424
+ h('div', { style: { display: 'flex', gap: 16, marginTop: 8, fontSize: 11 } },
425
+ h('span', { style: { display: 'flex', alignItems: 'center', gap: 4 } }, h('span', { style: { width: 10, height: 10, borderRadius: 2, background: 'var(--success, #15803d)', display: 'inline-block' } }), 'Revenue'),
426
+ h('span', { style: { display: 'flex', alignItems: 'center', gap: 4 } }, h('span', { style: { width: 10, height: 10, borderRadius: 2, background: 'var(--danger)', opacity: 0.7, display: 'inline-block' } }), 'Token Cost'),
427
+ (function() {
428
+ var totRev = billingSummary.reduce(function(a, m) { return a + (parseFloat(m.total_revenue) || 0); }, 0);
429
+ var totCost = billingSummary.reduce(function(a, m) { return a + (parseFloat(m.total_cost) || 0); }, 0);
430
+ return h('span', { style: { marginLeft: 'auto', fontWeight: 600, color: (totRev - totCost) >= 0 ? 'var(--success, #15803d)' : 'var(--danger)' } },
431
+ 'Net: ' + (detailOrg.currency || 'USD') + ' ' + (totRev - totCost).toFixed(2)
432
+ );
433
+ })()
434
+ )
435
+ ),
436
+
437
+ billingSummary.length === 0 && h('div', { style: { padding: 24, textAlign: 'center', color: 'var(--text-muted)', fontSize: 13, background: 'var(--bg-tertiary)', borderRadius: 8, marginBottom: 16 } },
438
+ 'No billing data yet. Billing records are created as agents process tasks and accumulate token costs.'
439
+ ),
440
+
441
+ // Stats summary
442
+ (function() {
443
+ var totRev = billingSummary.reduce(function(a, m) { return a + (parseFloat(m.total_revenue) || 0); }, 0);
444
+ var totCost = billingSummary.reduce(function(a, m) { return a + (parseFloat(m.total_cost) || 0); }, 0);
445
+ var totIn = billingSummary.reduce(function(a, m) { return a + (parseInt(m.total_input_tokens) || 0); }, 0);
446
+ var totOut = billingSummary.reduce(function(a, m) { return a + (parseInt(m.total_output_tokens) || 0); }, 0);
447
+ var margin = totRev > 0 ? ((totRev - totCost) / totRev * 100) : 0;
448
+ return h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 16 } },
449
+ h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, textAlign: 'center' } },
450
+ h('div', { style: { fontSize: 18, fontWeight: 700, color: 'var(--success, #15803d)' } }, (detailOrg.currency || 'USD') + ' ' + totRev.toFixed(2)),
451
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Total Revenue')
452
+ ),
453
+ h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, textAlign: 'center' } },
454
+ h('div', { style: { fontSize: 18, fontWeight: 700, color: 'var(--danger)' } }, (detailOrg.currency || 'USD') + ' ' + totCost.toFixed(4)),
455
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Token Cost')
456
+ ),
457
+ h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, textAlign: 'center' } },
458
+ h('div', { style: { fontSize: 18, fontWeight: 700, color: margin >= 0 ? 'var(--success, #15803d)' : 'var(--danger)' } }, margin.toFixed(1) + '%'),
459
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Margin')
460
+ ),
461
+ h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, textAlign: 'center' } },
462
+ h('div', { style: { fontSize: 18, fontWeight: 700 } }, ((totIn + totOut) / 1000).toFixed(1) + 'K'),
463
+ h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, 'Total Tokens')
464
+ )
465
+ );
466
+ })(),
467
+
468
+ // Per-agent breakdown table
469
+ billingRecords.length > 0 && h(Fragment, null,
470
+ h('div', { style: { fontSize: 14, fontWeight: 700, marginBottom: 8 } }, 'Records'),
471
+ h('div', { style: { overflowX: 'auto' } },
472
+ h('table', null,
473
+ h('thead', null, h('tr', null,
474
+ h('th', null, 'Month'), h('th', null, 'Agent'), h('th', { style: { textAlign: 'right' } }, 'Revenue'), h('th', { style: { textAlign: 'right' } }, 'Token Cost'), h('th', { style: { textAlign: 'right' } }, 'Profit'), h('th', { style: { textAlign: 'right' } }, 'Tokens')
475
+ )),
476
+ h('tbody', null,
477
+ billingRecords.map(function(r, i) {
478
+ var rev = parseFloat(r.revenue) || 0;
479
+ var cost = parseFloat(r.token_cost) || 0;
480
+ var agent = detailAgents.find(function(a) { return a.id === r.agent_id; });
481
+ return h('tr', { key: i },
482
+ h('td', { style: { fontFamily: 'var(--font-mono)', fontSize: 12 } }, r.month),
483
+ h('td', null, agent ? agent.name : (r.agent_id ? r.agent_id.slice(0, 8) : 'All')),
484
+ h('td', { style: { textAlign: 'right', color: 'var(--success, #15803d)' } }, rev.toFixed(2)),
485
+ h('td', { style: { textAlign: 'right', color: 'var(--danger)' } }, cost.toFixed(4)),
486
+ h('td', { style: { textAlign: 'right', fontWeight: 600, color: (rev - cost) >= 0 ? 'var(--success, #15803d)' : 'var(--danger)' } }, (rev - cost).toFixed(2)),
487
+ h('td', { style: { textAlign: 'right', fontSize: 12, color: 'var(--text-muted)' } }, ((parseInt(r.input_tokens) || 0) + (parseInt(r.output_tokens) || 0)).toLocaleString())
488
+ );
489
+ })
490
+ )
491
+ )
492
+ )
493
+ )
494
+ ) // end billing tab
342
495
  )
343
496
  )
344
497
  );
@@ -2,8 +2,11 @@ import { h, useState, useEffect, useCallback, Fragment, useApp, engineCall, getO
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 SkillsPage() {
8
+ var orgCtx = useOrgContext();
9
+ var effectiveOrgId = orgCtx.selectedOrgId || getOrgId();
7
10
  var app = useApp();
8
11
  var toast = app.toast;
9
12
  var setPage = app.setPage;
@@ -66,7 +69,7 @@ export function SkillsPage() {
66
69
  // Load installed skills + statuses
67
70
  var loadInstalled = useCallback(function() {
68
71
  setInstalledLoading(true);
69
- engineCall('/community/installed?orgId=' + getOrgId())
72
+ engineCall('/community/installed?orgId=' + effectiveOrgId)
70
73
  .then(function(d) {
71
74
  var items = d.installed || [];
72
75
  setInstalled(items);
@@ -162,7 +165,7 @@ export function SkillsPage() {
162
165
  var payload = isMultiField
163
166
  ? { credentials: tokenModal.fields.reduce(function(o, f) { o[f] = credFields[f].trim(); return o; }, {}) }
164
167
  : { token: tokenValue };
165
- await engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + getOrgId(), {
168
+ await engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId, {
166
169
  method: 'POST',
167
170
  body: JSON.stringify(payload)
168
171
  });
@@ -183,7 +186,7 @@ export function SkillsPage() {
183
186
  try {
184
187
  await engineCall('/community/skills/' + skillId + '/' + (enable ? 'enable' : 'disable'), {
185
188
  method: 'PUT',
186
- body: JSON.stringify({ orgId: getOrgId() })
189
+ body: JSON.stringify({ orgId: effectiveOrgId })
187
190
  });
188
191
  toast('Skill ' + (enable ? 'enabled' : 'disabled'), 'success');
189
192
  loadInstalled();
@@ -201,7 +204,7 @@ export function SkillsPage() {
201
204
  try {
202
205
  await engineCall('/community/skills/' + skillId + '/uninstall', {
203
206
  method: 'DELETE',
204
- body: JSON.stringify({ orgId: getOrgId() })
207
+ body: JSON.stringify({ orgId: effectiveOrgId })
205
208
  });
206
209
  toast('Skill uninstalled', 'success');
207
210
  loadInstalled();
@@ -240,7 +243,7 @@ export function SkillsPage() {
240
243
  try {
241
244
  await engineCall('/community/skills/' + skillId + '/install', {
242
245
  method: 'POST',
243
- body: JSON.stringify({ orgId: getOrgId() })
246
+ body: JSON.stringify({ orgId: effectiveOrgId })
244
247
  });
245
248
  toast('Skill installed', 'success');
246
249
  loadInstalled();
@@ -269,7 +272,7 @@ export function SkillsPage() {
269
272
 
270
273
  var loadIntegrations = useCallback(function() {
271
274
  setIntLoading(true);
272
- engineCall('/integrations/catalog?orgId=' + getOrgId())
275
+ engineCall('/integrations/catalog?orgId=' + effectiveOrgId)
273
276
  .then(function(d) {
274
277
  setIntegrations(d.catalog || []);
275
278
  setIntCategories(d.categories || []);
@@ -302,7 +305,7 @@ export function SkillsPage() {
302
305
  setOauthSaving(false);
303
306
  // For OAuth2, check if app is already configured
304
307
  if (int.authType === 'oauth2' && int.oauthProvider) {
305
- engineCall('/oauth/app-config/' + int.oauthProvider + '?orgId=' + getOrgId())
308
+ engineCall('/oauth/app-config/' + int.oauthProvider + '?orgId=' + effectiveOrgId)
306
309
  .then(function(d) { if (d.configured) setOauthAppConfigured(true); })
307
310
  .catch(function() {});
308
311
  }
@@ -312,14 +315,14 @@ export function SkillsPage() {
312
315
  if (!tokenModal || !tokenModal.oauthProvider) return;
313
316
  if (!oauthClientId.trim() || !oauthClientSecret.trim()) return;
314
317
  setOauthSaving(true);
315
- engineCall('/oauth/app-config/' + tokenModal.oauthProvider + '?orgId=' + getOrgId(), {
318
+ engineCall('/oauth/app-config/' + tokenModal.oauthProvider + '?orgId=' + effectiveOrgId, {
316
319
  method: 'POST',
317
320
  body: JSON.stringify({ clientId: oauthClientId.trim(), clientSecret: oauthClientSecret.trim() })
318
321
  })
319
322
  .then(function() {
320
323
  setOauthAppConfigured(true);
321
324
  toast('OAuth app configured for ' + tokenModal.skill.name, 'success');
322
- return engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + getOrgId());
325
+ return engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId);
323
326
  })
324
327
  .then(function(d) {
325
328
  if (d && (d.authUrl || d.authorizationUrl)) {
@@ -336,7 +339,7 @@ export function SkillsPage() {
336
339
 
337
340
  var launchOauthFlow = function() {
338
341
  if (!tokenModal) return;
339
- engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + getOrgId())
342
+ engineCall('/oauth/authorize/' + tokenModal.skillId + '?orgId=' + effectiveOrgId)
340
343
  .then(function(d) {
341
344
  if (d && (d.authUrl || d.authorizationUrl)) {
342
345
  var popup = window.open(d.authUrl || d.authorizationUrl, 'oauth_connect', 'width=600,height=700,popup=yes');
@@ -353,7 +356,7 @@ export function SkillsPage() {
353
356
 
354
357
  var disconnectIntegration = function(int) {
355
358
  if (!confirm('Disconnect ' + int.name + '? Agents will lose access to its tools.')) return;
356
- engineCall('/oauth/disconnect/' + int.skillId + '?orgId=' + getOrgId(), { method: 'DELETE' })
359
+ engineCall('/oauth/disconnect/' + int.skillId + '?orgId=' + effectiveOrgId, { method: 'DELETE' })
357
360
  .then(function() { toast(int.name + ' disconnected', 'success'); loadIntegrations(); })
358
361
  .catch(function(e) { toast('Failed: ' + e.message, 'error'); });
359
362
  };
@@ -380,6 +383,7 @@ export function SkillsPage() {
380
383
  // ── Builtin Tab ──
381
384
  var renderBuiltin = function() {
382
385
  return h(Fragment, null,
386
+ h(orgCtx.Switcher),
383
387
  h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
384
388
  h('div', { style: { position: 'relative', flex: 1, maxWidth: 320 } },
385
389
  h('input', {
@@ -1,6 +1,7 @@
1
1
  import { h, useState, useEffect, useCallback, useRef, Fragment, useApp, engineCall, 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
  var NODE_W = 200;
@@ -438,6 +439,8 @@ export function TaskPipelinePage() {
438
439
  injectCSS();
439
440
  var app = useApp();
440
441
  var toast = app.toast;
442
+ var orgCtx = useOrgContext();
443
+ var effectiveOrgId = orgCtx.selectedOrgId || effectiveOrgId;
441
444
  var _tasks = useState([]);
442
445
  var tasks = _tasks[0]; var setTasks = _tasks[1];
443
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: [] });
@@ -474,7 +477,7 @@ export function TaskPipelinePage() {
474
477
  Promise.all([
475
478
  engineCall('/task-pipeline?limit=200'),
476
479
  engineCall('/task-pipeline/stats'),
477
- engineCall('/agents?orgId=' + getOrgId()).catch(function() { return { agents: [] }; }),
480
+ engineCall('/agents?orgId=' + effectiveOrgId).catch(function() { return { agents: [] }; }),
478
481
  ]).then(function(res) {
479
482
  setTasks(res[0]?.tasks || []);
480
483
  setStats(res[1] || stats);
@@ -486,7 +489,7 @@ export function TaskPipelinePage() {
486
489
  setAgentMap(map);
487
490
  }).catch(function(err) { console.error('[TaskPipeline]', err); })
488
491
  .finally(function() { setLoading(false); });
489
- }, []);
492
+ }, [effectiveOrgId]);
490
493
 
491
494
  // SSE
492
495
  useEffect(function() {
@@ -1074,6 +1077,7 @@ export function AgentTaskPipeline(props) {
1074
1077
  }
1075
1078
 
1076
1079
  return h(Fragment, null,
1080
+ h(orgCtx.Switcher),
1077
1081
  active.length > 0 && h('div', { style: { marginBottom: 12 } },
1078
1082
  h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
1079
1083
  h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),