@miller-tech/uap 1.133.2 → 1.135.1
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/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +4 -1
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/config-command.d.ts +49 -0
- package/dist/cli/config-command.d.ts.map +1 -0
- package/dist/cli/config-command.js +465 -0
- package/dist/cli/config-command.js.map +1 -0
- package/dist/cli/config-wizard.d.ts +13 -0
- package/dist/cli/config-wizard.d.ts.map +1 -0
- package/dist/cli/config-wizard.js +134 -0
- package/dist/cli/config-wizard.js.map +1 -0
- package/dist/cli/deliver.js +11 -2
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/guided-setup.d.ts.map +1 -1
- package/dist/cli/guided-setup.js +11 -0
- package/dist/cli/guided-setup.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +34 -0
- package/dist/cli/policy.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +22 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/policy-recommendations.d.ts +29 -0
- package/dist/config/policy-recommendations.d.ts.map +1 -0
- package/dist/config/policy-recommendations.js +108 -0
- package/dist/config/policy-recommendations.js.map +1 -0
- package/dist/config/settings-registry.d.ts +65 -0
- package/dist/config/settings-registry.d.ts.map +1 -0
- package/dist/config/settings-registry.js +334 -0
- package/dist/config/settings-registry.js.map +1 -0
- package/dist/dashboard/controls.d.ts +66 -0
- package/dist/dashboard/controls.d.ts.map +1 -0
- package/dist/dashboard/controls.js +188 -0
- package/dist/dashboard/controls.js.map +1 -0
- package/dist/dashboard/data-service.d.ts +20 -0
- package/dist/dashboard/data-service.d.ts.map +1 -1
- package/dist/dashboard/data-service.js +47 -0
- package/dist/dashboard/data-service.js.map +1 -1
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +81 -5
- package/dist/dashboard/server.js.map +1 -1
- package/dist/delivery/convergence-loop.d.ts +5 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +11 -0
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/run-state.d.ts +12 -0
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +58 -1
- package/dist/delivery/run-state.js.map +1 -1
- package/docs/DASHBOARD_UPLIFT_SPEC.md +171 -0
- package/docs/INDEX.md +2 -0
- package/docs/guides/POLICY_SELECTION.md +98 -0
- package/docs/reference/CONFIGURATION_REFERENCE.md +688 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +164 -12
- package/web/dash/core.js +398 -0
- package/web/dash/styles.css +307 -0
- package/web/dash/tab-deliver.js +12 -0
- package/web/dash/tab-memory.js +12 -0
- package/web/dash/tab-models.js +12 -0
- package/web/dash/tab-policies.js +12 -0
- package/web/dash/tabs.js +581 -0
- package/web/dashboard.html +12 -1303
package/web/dash/tabs.js
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/* UAP Dashboard tabs — 8 views registered against the core UAP shell.
|
|
2
|
+
* Loaded after core.js; calls UAP.start() at the end. */
|
|
3
|
+
(function () {
|
|
4
|
+
'use strict';
|
|
5
|
+
var U = window.UAP;
|
|
6
|
+
var el = U.el, esc = U.esc, fmtNum = U.fmtNum, fmtUsd = U.fmtUsd, fmtKB = U.fmtKB, modelName = U.modelName;
|
|
7
|
+
|
|
8
|
+
// ── shared render helpers ──
|
|
9
|
+
function h(tag, cls, txt) { return el(tag, { class: cls, text: txt }); }
|
|
10
|
+
function kv(label, value, cls) { return el('div', { class: 'kv' }, el('span', { class: 'label', text: label }), el('span', { class: 'value ' + (cls || ''), text: value })); }
|
|
11
|
+
function empty(msg) { return el('div', { class: 'empty', text: msg || 'No data' }); }
|
|
12
|
+
function panel(title, actions) {
|
|
13
|
+
var head = el('h2', {}, title);
|
|
14
|
+
if (actions) { var wrap = el('span', { class: 'h2-actions' }); actions.forEach(function (a) { wrap.appendChild(a); }); head.appendChild(wrap); }
|
|
15
|
+
var p = el('div', { class: 'panel' }, head);
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
function statusBadge(s) { s = s || 'idle'; var map = { active: 'active', in_progress: 'active', completed: 'completed', done: 'completed', running: 'running', delivered: 'delivered', failed: 'failed', interrupted: 'interrupted', blocked: 'failed', idle: 'idle', open: 'idle', pending: 'idle', wont_do: 'idle' }; return el('span', { class: 'badge ' + (map[s] || 'idle'), text: s }); }
|
|
19
|
+
// Build a <table> node from header list + row builder returning an array of cells (nodes/strings).
|
|
20
|
+
function tableNode(headers, items, rowFn, rowClick) {
|
|
21
|
+
var thead = el('tr', {}); headers.forEach(function (hd) { thead.appendChild(el('th', { text: hd })); });
|
|
22
|
+
var t = el('table', {}, thead);
|
|
23
|
+
items.forEach(function (it, i) {
|
|
24
|
+
var cells = rowFn(it, i);
|
|
25
|
+
var tr = el('tr', rowClick ? { class: 'clickable', onclick: function () { rowClick(it); } } : {});
|
|
26
|
+
cells.forEach(function (c) { tr.appendChild(el('td', {}, c)); });
|
|
27
|
+
t.appendChild(tr);
|
|
28
|
+
});
|
|
29
|
+
return el('div', { class: 'table-wrap' }, t);
|
|
30
|
+
}
|
|
31
|
+
function sig(obj) { try { return JSON.stringify(obj); } catch (e) { return String(Date.now()); } }
|
|
32
|
+
// DeliverRunState has no scalar 'phase'; derive a display string from phases/checkpoint.
|
|
33
|
+
function runPhase(r) {
|
|
34
|
+
if (r.phases && r.phases.length) { var i = r.phaseIndex || 0; var ph = r.phases[Math.min(i, r.phases.length - 1)]; if (ph) return 'phase ' + (i + 1) + '/' + r.phases.length + ': ' + (ph.title || ph.id); }
|
|
35
|
+
if (r.checkpoint && r.checkpoint.turn) return 'turn ' + r.checkpoint.turn;
|
|
36
|
+
return '-';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ═══════════════════════════ OVERVIEW ═══════════════════════════
|
|
40
|
+
U.registerTab('overview', (function () {
|
|
41
|
+
function tile(label, value, cls, sub, goId) {
|
|
42
|
+
return el('div', { class: 'tile', tabindex: '0', role: 'button', onclick: function () { if (goId) U.goto(goId); }, onkeydown: function (e) { if ((e.key === 'Enter' || e.key === ' ') && goId) { e.preventDefault(); U.goto(goId); } } },
|
|
43
|
+
el('div', { class: 'tile-label', text: label }), el('div', { class: 'tile-value ' + (cls || ''), text: value }), sub ? el('div', { class: 'tile-sub', text: sub }) : null);
|
|
44
|
+
}
|
|
45
|
+
function fillTiles(host, d) {
|
|
46
|
+
var t = d.tasks || {}, coord = d.coordination || {}, runs = d.deliverRuns || [], led = d.orchestrationTree && d.orchestrationTree.ledger, sv = d.savingsByInfluence || {}, mem = d.memory || {}, comp = d.compliance || {};
|
|
47
|
+
var pct = t.total ? Math.round((t.done / t.total) * 100) : 0;
|
|
48
|
+
var running = runs.filter(function (r) { return r.status === 'running'; }).length;
|
|
49
|
+
var orchPct = led ? (led.total ? Math.round((led.done / led.total) * 100) : 0) : ((d.orchestrationTree && d.orchestrationTree.ledger) ? d.orchestrationTree.ledger.pct : 0);
|
|
50
|
+
var memEntries = ((mem.l1 && mem.l1.entries) || 0) + ((mem.l2 && mem.l2.entries) || 0) + ((mem.l4 && mem.l4.entities) || 0);
|
|
51
|
+
U.clear(host);
|
|
52
|
+
[
|
|
53
|
+
tile('Tasks', pct + '%', pct >= 100 ? 'green' : 'cyan', (t.done || 0) + '/' + (t.total || 0) + ' done', 'tasks'),
|
|
54
|
+
tile('Active Agents', String(coord.activeAgents || 0), 'purple', (coord.totalAgents || 0) + ' total', 'agents'),
|
|
55
|
+
tile('Deliver Runs', String(running), running ? 'cyan' : '', (runs.length) + ' tracked', 'deliver'),
|
|
56
|
+
tile('Orchestration', orchPct + '%', orchPct >= 100 ? 'green' : 'cyan', led ? (led.done + '/' + led.total + ' items') : 'no active build', 'orchestration'),
|
|
57
|
+
tile('Tokens Saved', fmtNum(sv.totalTokensSaved || 0), 'green', fmtUsd(sv.totalCostSavedUsd || 0) + ' saved', 'memory'),
|
|
58
|
+
tile('Memory', fmtNum(memEntries), 'cyan', ((mem.l3 && mem.l3.status) || 'Qdrant') + '', 'memory'),
|
|
59
|
+
tile('Policy Blocks', String(comp.totalBlocks || 0), (comp.totalBlocks ? 'red' : 'green'), (comp.blockRate || '0%') + ' rate', 'policies'),
|
|
60
|
+
tile('Worktrees', String(coord.activeWorktrees || 0), '', (coord.activeClaims || 0) + ' claims', 'agents'),
|
|
61
|
+
].forEach(function (n) { host.appendChild(n); });
|
|
62
|
+
}
|
|
63
|
+
function fillSummaries(host, d) {
|
|
64
|
+
U.clear(host);
|
|
65
|
+
var coord = d.coordination || {}, agents = (coord.agents || []).slice(0, 8);
|
|
66
|
+
var ap = panel('Active Agents');
|
|
67
|
+
ap.appendChild(agents.length ? tableNode(['Name', 'Status', 'Task', 'Started'], agents, function (a) {
|
|
68
|
+
return [a.name || a.id || '?', statusBadge(a.status), el('span', { class: 'muted', text: (a.task || '-') }), U.timeAgo(a.startedAt)];
|
|
69
|
+
}, function () { U.goto('agents'); }) : empty('No active agents'));
|
|
70
|
+
host.appendChild(ap);
|
|
71
|
+
|
|
72
|
+
var runs = (d.deliverRuns || []).slice(0, 6);
|
|
73
|
+
var rp = panel('Deliver Runs');
|
|
74
|
+
rp.appendChild(runs.length ? tableNode(['Run', 'Status', 'Phase', 'Updated'], runs, function (r) {
|
|
75
|
+
return [el('span', { class: 'mono-sm', text: (r.runId || '').slice(0, 10) }), statusBadge(r.status), runPhase(r), U.timeAgo(r.updatedAt || r.createdAt)];
|
|
76
|
+
}, function () { U.goto('deliver'); }) : empty('No deliver runs tracked'));
|
|
77
|
+
host.appendChild(rp);
|
|
78
|
+
|
|
79
|
+
var led = d.orchestrationTree && d.orchestrationTree.ledger;
|
|
80
|
+
var op = panel('Orchestration');
|
|
81
|
+
if (led && led.total) {
|
|
82
|
+
var pct = led.pct != null ? led.pct : Math.round((led.done / led.total) * 100);
|
|
83
|
+
op.appendChild(el('div', {}, el('div', { class: 'kv' }, el('span', { class: 'label', text: (led.mission || 'Active build').slice(0, 60) }), el('span', { class: 'value cyan', text: led.done + '/' + led.total + ' (' + pct + '%)' })),
|
|
84
|
+
el('div', { class: 'progress-track' }, el('div', { class: 'progress-fill', style: { width: pct + '%' } }))));
|
|
85
|
+
} else op.appendChild(empty('No active orchestration'));
|
|
86
|
+
host.appendChild(op);
|
|
87
|
+
|
|
88
|
+
var ev = (U.liveEvents || []).slice(0, 10);
|
|
89
|
+
var ep = panel('Recent Activity');
|
|
90
|
+
var feed = el('div', { class: 'event-feed' });
|
|
91
|
+
if (ev.length) ev.forEach(function (e) { feed.appendChild(eventRow(e)); }); else feed.appendChild(empty('Waiting for events…'));
|
|
92
|
+
ep.appendChild(feed); host.appendChild(ep);
|
|
93
|
+
}
|
|
94
|
+
function build(root, d) {
|
|
95
|
+
var tiles = el('div', { class: 'tile-grid', id: 'ov-tiles' });
|
|
96
|
+
var hero = el('div', { class: 'hero-row' },
|
|
97
|
+
panelWith('Tasks & Agents', el('div', { class: 'chart-container chart-hero', id: 'ov-chart-tasks' })),
|
|
98
|
+
panelWith('Compression & Memory', el('div', { class: 'chart-container chart-hero', id: 'ov-chart-comp' })));
|
|
99
|
+
var summaries = el('div', { class: 'metrics-row', id: 'ov-summaries', style: { gridTemplateColumns: 'repeat(auto-fit,minmax(280px,1fr))' } });
|
|
100
|
+
root.appendChild(tiles); root.appendChild(hero); root.appendChild(summaries);
|
|
101
|
+
fillTiles(tiles, d); fillSummaries(summaries, d); paintCharts(d);
|
|
102
|
+
}
|
|
103
|
+
function panelWith(title, node) { var p = panel(title); p.appendChild(node); return p; }
|
|
104
|
+
function paintCharts(d) { var ts = d.timeSeries || []; U.charts.syncHero('ov-chart-tasks', 'tasks', ts); U.charts.syncHero('ov-chart-comp', 'comp', ts); }
|
|
105
|
+
return {
|
|
106
|
+
label: 'Overview',
|
|
107
|
+
render: function (root, d) { build(root, d); },
|
|
108
|
+
update: function (root, d) { var t = document.getElementById('ov-tiles'), s = document.getElementById('ov-summaries'); if (!t || !s) { U.clear(root); build(root, d); return; } fillTiles(t, d); fillSummaries(s, d); paintCharts(d); },
|
|
109
|
+
};
|
|
110
|
+
})());
|
|
111
|
+
|
|
112
|
+
function eventRow(ev) {
|
|
113
|
+
var cat = ev.category || ev.type || 'system';
|
|
114
|
+
var ts = ev.timestamp ? new Date(ev.timestamp).toLocaleTimeString() : '';
|
|
115
|
+
var title = ev.title || ev.message || '';
|
|
116
|
+
var detail = ev.detail || '';
|
|
117
|
+
var msg = (title && detail) ? (title + ' — ' + detail) : (title || detail || String((ev.data && JSON.stringify(ev.data)) || '').slice(0, 80));
|
|
118
|
+
return el('div', { class: 'event-row' }, el('span', { class: 'event-time', text: ts }), el('span', { class: 'event-cat cat-' + cat, text: cat }), el('span', { class: 'event-msg', text: msg }));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ═══════════════════════════ TASKS & EPICS ═══════════════════════════
|
|
122
|
+
U.registerTab('tasks', (function () {
|
|
123
|
+
var TYPE_ICONS = { task: '◆', bug: '🐛', feature: '✨', epic: '🎯', chore: '🔧', story: '📖' };
|
|
124
|
+
var PRIO = ['P0', 'P1', 'P2', 'P3', 'P4'];
|
|
125
|
+
var COLS = [['open', 'Open'], ['in_progress', 'In Progress'], ['blocked', 'Blocked'], ['done', 'Done'], ['wont_do', "Won't Do"]];
|
|
126
|
+
var filterStatus = 'all', lastSig = null;
|
|
127
|
+
|
|
128
|
+
function openTaskDrawer(task, d) {
|
|
129
|
+
var body = el('div', {});
|
|
130
|
+
body.appendChild(kv('ID', task.id, 'cyan'));
|
|
131
|
+
body.appendChild(kv('Type', task.type || 'task'));
|
|
132
|
+
body.appendChild(kv('Status', task.status || '-'));
|
|
133
|
+
body.appendChild(kv('Priority', PRIO[task.priority] || 'P2'));
|
|
134
|
+
body.appendChild(kv('Assignee', task.assignee || 'unassigned'));
|
|
135
|
+
if (task.parentTitle) body.appendChild(kv('Parent', task.parentTitle));
|
|
136
|
+
body.appendChild(el('h3', {}, 'Title'));
|
|
137
|
+
body.appendChild(el('div', { class: 'muted', text: task.title || '-' }));
|
|
138
|
+
|
|
139
|
+
body.appendChild(el('h3', {}, 'Change status'));
|
|
140
|
+
var statusRow = el('div', { class: 'toolbar' });
|
|
141
|
+
['open', 'in_progress', 'blocked', 'done', 'wont_do'].forEach(function (s) {
|
|
142
|
+
statusRow.appendChild(el('button', { class: 'btn' + (s === task.status ? ' btn-primary' : ''), onclick: function () { U.api('/api/tasks/' + encodeURIComponent(task.id) + '/update', { status: s }).then(function () { U.toast('Status → ' + s, 'ok'); U.closeDrawer(); }); } }, s));
|
|
143
|
+
});
|
|
144
|
+
body.appendChild(statusRow);
|
|
145
|
+
|
|
146
|
+
body.appendChild(el('h3', {}, 'Actions'));
|
|
147
|
+
var actions = el('div', { class: 'toolbar' });
|
|
148
|
+
actions.appendChild(el('button', { class: 'btn', onclick: function () {
|
|
149
|
+
U.form('Set assignee', [{ name: 'assignee', label: 'Assignee', value: task.assignee || '' }]).then(function (v) { if (v) U.api('/api/tasks/' + encodeURIComponent(task.id) + '/update', { assignee: v.assignee }).then(function () { U.toast('Assignee updated', 'ok'); U.closeDrawer(); }); });
|
|
150
|
+
} }, 'Set assignee'));
|
|
151
|
+
actions.appendChild(el('button', { class: 'btn', onclick: function () {
|
|
152
|
+
U.form('Edit title', [{ name: 'title', label: 'Title', value: task.title || '' }]).then(function (v) { if (v) U.api('/api/tasks/' + encodeURIComponent(task.id) + '/update', { title: v.title }).then(function () { U.toast('Title updated', 'ok'); U.closeDrawer(); }); });
|
|
153
|
+
} }, 'Edit title'));
|
|
154
|
+
if (task.status !== 'done') actions.appendChild(el('button', { class: 'btn btn-primary', onclick: function () { U.api('/api/tasks/' + encodeURIComponent(task.id) + '/close', {}).then(function () { U.toast('Task closed', 'ok'); U.closeDrawer(); }); } }, 'Close (done)'));
|
|
155
|
+
actions.appendChild(el('button', { class: 'btn btn-danger', onclick: function () {
|
|
156
|
+
U.confirm('Delete task ' + task.id + '? This removes its dependencies, history and activity. This cannot be undone.', { danger: true }).then(function (ok) { if (ok) U.api('/api/tasks/' + encodeURIComponent(task.id) + '/delete', {}).then(function () { U.toast('Task deleted', 'ok'); U.closeDrawer(); }); });
|
|
157
|
+
} }, 'Delete'));
|
|
158
|
+
body.appendChild(actions);
|
|
159
|
+
|
|
160
|
+
var epicLed = d.orchestrationTree && d.orchestrationTree.ledger;
|
|
161
|
+
if (task.type === 'epic' && epicLed && epicLed.items) {
|
|
162
|
+
body.appendChild(el('h3', {}, 'Epic ledger'));
|
|
163
|
+
body.appendChild(ledgerItems(epicLed));
|
|
164
|
+
}
|
|
165
|
+
U.drawer((TYPE_ICONS[task.type] || '◆') + ' ' + (task.title || task.id).slice(0, 40), body);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function card(item, d) {
|
|
169
|
+
var pc = 'p' + (item.priority != null ? item.priority : 2);
|
|
170
|
+
var crumb = (item.depth || 0) > 1 && item.parentId ? el('div', { class: 'card-crumb', text: '↳ ' + (item.parentTitle || item.parentId) }) : null;
|
|
171
|
+
var c = el('div', { class: 'kanban-card' + ((item.depth || 0) > 0 ? ' card-child' : ''), dataset: { id: item.id }, onclick: function () { openTaskDrawer(item, d); } },
|
|
172
|
+
el('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } }, el('span', { class: 'card-id', text: item.id }), el('span', {}, TYPE_ICONS[item.type] || '◆')),
|
|
173
|
+
crumb,
|
|
174
|
+
el('div', { class: 'card-title', text: item.title || 'Untitled' }),
|
|
175
|
+
el('div', { class: 'card-meta' }, el('span', { class: 'card-priority ' + pc, text: PRIO[item.priority] || 'P2' }), el('span', { text: (item.type || 'task') + (item.assignee ? ' · ' + item.assignee : '') })));
|
|
176
|
+
return c;
|
|
177
|
+
}
|
|
178
|
+
function renderBoard(board, d) {
|
|
179
|
+
var tasks = d.tasks || {}, items = tasks.items || [];
|
|
180
|
+
if (filterStatus !== 'all') items = items.filter(function (i) { return i.status === filterStatus; });
|
|
181
|
+
var byCol = {}; COLS.forEach(function (c) { byCol[c[0]] = []; });
|
|
182
|
+
var groupCounts = {}, families = {};
|
|
183
|
+
items.forEach(function (i) { if (byCol[i.status]) byCol[i.status].push(i); var g = i.groupId || i.id; groupCounts[g] = (groupCounts[g] || 0) + 1; if ((i.depth || 0) > 0) families[g] = true; });
|
|
184
|
+
for (var g in groupCounts) if (groupCounts[g] > 1) families[g] = true;
|
|
185
|
+
U.clear(board);
|
|
186
|
+
COLS.forEach(function (col) {
|
|
187
|
+
var list = byCol[col[0]] || [];
|
|
188
|
+
var colEl = el('div', { class: 'kanban-col' }, el('div', { class: 'kanban-col-header ' + col[0] }, el('span', { text: col[1] }), el('span', { class: 'col-count', text: String(list.length) })));
|
|
189
|
+
var cards = el('div', { class: 'kanban-cards' });
|
|
190
|
+
if (!list.length) cards.appendChild(el('div', { class: 'kanban-empty', text: 'No tasks' }));
|
|
191
|
+
else {
|
|
192
|
+
var sorted = list.slice().sort(function (a, b) {
|
|
193
|
+
var ga = families[a.groupId || a.id] ? 0 : 1, gb = families[b.groupId || b.id] ? 0 : 1;
|
|
194
|
+
if (ga !== gb) return ga - gb;
|
|
195
|
+
if ((a.groupId || a.id) !== (b.groupId || b.id)) return String(a.groupTitle || a.title || '').localeCompare(String(b.groupTitle || b.title || '')) || String(a.groupId || a.id).localeCompare(String(b.groupId || b.id));
|
|
196
|
+
if ((a.depth || 0) !== (b.depth || 0)) return (a.depth || 0) - (b.depth || 0);
|
|
197
|
+
return (a.priority != null ? a.priority : 2) - (b.priority != null ? b.priority : 2);
|
|
198
|
+
});
|
|
199
|
+
var lastGroup = null;
|
|
200
|
+
sorted.forEach(function (item) {
|
|
201
|
+
var gid = item.groupId || item.id;
|
|
202
|
+
if (families[gid] && gid !== lastGroup) cards.appendChild(el('div', { class: 'kanban-group', text: '▸ ' + (item.groupTitle || gid) }));
|
|
203
|
+
lastGroup = gid;
|
|
204
|
+
cards.appendChild(card(item, d));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
colEl.appendChild(cards); board.appendChild(colEl);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function build(root, d) {
|
|
211
|
+
var tasks = d.tasks || {};
|
|
212
|
+
var filterSel = el('select', { onchange: function (e) { filterStatus = e.target.value; var b = document.getElementById('kanban-board'); if (b) renderBoard(b, U.state || d); } });
|
|
213
|
+
[['all', 'All statuses']].concat(COLS.map(function (c) { return [c[0], c[1]]; })).forEach(function (o) { filterSel.appendChild(el('option', { value: o[0] }, o[1])); });
|
|
214
|
+
filterSel.value = filterStatus;
|
|
215
|
+
var p = panel('Task Board', [
|
|
216
|
+
el('button', { class: 'btn btn-primary', onclick: function () {
|
|
217
|
+
U.form('New task', [
|
|
218
|
+
{ name: 'title', label: 'Title', placeholder: 'What needs doing' },
|
|
219
|
+
{ name: 'type', label: 'Type', type: 'select', options: ['task', 'feature', 'bug', 'epic', 'chore', 'story'] },
|
|
220
|
+
{ name: 'priority', label: 'Priority (0-4)', type: 'number', value: '2' },
|
|
221
|
+
{ name: 'assignee', label: 'Assignee (optional)' },
|
|
222
|
+
]).then(function (v) { if (v && v.title) U.api('/api/tasks', { title: v.title, type: v.type, priority: Number(v.priority) || 2, assignee: v.assignee || undefined }).then(function () { U.toast('Task created', 'ok'); }); });
|
|
223
|
+
} }, '+ New Task'),
|
|
224
|
+
]);
|
|
225
|
+
var counts = el('div', { class: 'section-note', text: (tasks.total || 0) + ' tasks · ' + (tasks.done || 0) + ' done · ' + (tasks.inProgress || 0) + ' active · ' + (tasks.blocked || 0) + ' blocked · ' + (tasks.open || 0) + ' open' });
|
|
226
|
+
p.appendChild(counts);
|
|
227
|
+
p.appendChild(el('div', { class: 'toolbar' }, el('span', { class: 'label', text: 'Filter:' }), filterSel));
|
|
228
|
+
var board = el('div', { class: 'kanban', id: 'kanban-board' });
|
|
229
|
+
p.appendChild(board);
|
|
230
|
+
root.appendChild(p);
|
|
231
|
+
lastSig = sig((d.tasks || {}).items);
|
|
232
|
+
renderBoard(board, d);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
label: 'Tasks & Epics',
|
|
236
|
+
render: function (root, d) { build(root, d); },
|
|
237
|
+
update: function (root, d) { var b = document.getElementById('kanban-board'); if (!b) { U.clear(root); build(root, d); return; } var s = sig((d.tasks || {}).items); if (s !== lastSig) { lastSig = s; renderBoard(b, d); } },
|
|
238
|
+
};
|
|
239
|
+
})());
|
|
240
|
+
|
|
241
|
+
function ledgerItems(led) {
|
|
242
|
+
var wrap = el('div', {});
|
|
243
|
+
(led.items || []).forEach(function (it) {
|
|
244
|
+
var row = el('div', { class: 'kv' },
|
|
245
|
+
el('span', { class: 'label' }, (it.kind === 'epic' ? '🎯 ' : '• ') + esc0(it.title || it.id), el('span', { class: 'mono-sm', text: ' ' + (it.status || '') })),
|
|
246
|
+
el('span', {},
|
|
247
|
+
el('button', { class: 'btn', title: 'Mark done', onclick: function () { U.api('/api/ledger/item/' + encodeURIComponent(it.id), { status: 'done' }).then(function () { U.toast('Item done', 'ok'); }); } }, '✓'),
|
|
248
|
+
' ',
|
|
249
|
+
el('button', { class: 'btn btn-danger', title: 'Mark failed', onclick: function () { U.api('/api/ledger/item/' + encodeURIComponent(it.id), { status: 'failed' }).then(function () { U.toast('Item failed', 'warn'); }); } }, '✗')));
|
|
250
|
+
wrap.appendChild(row);
|
|
251
|
+
});
|
|
252
|
+
return wrap;
|
|
253
|
+
}
|
|
254
|
+
function esc0(s) { return s == null ? '' : String(s); }
|
|
255
|
+
|
|
256
|
+
// ═══════════════════════════ AGENTS & SESSIONS ═══════════════════════════
|
|
257
|
+
U.registerTab('agents', (function () {
|
|
258
|
+
var lastSig = null;
|
|
259
|
+
function mergeAgents(d) {
|
|
260
|
+
var out = {}, order = [];
|
|
261
|
+
var coord = (d.coordination && d.coordination.agents) || [];
|
|
262
|
+
coord.forEach(function (a) { out[a.id] = { id: a.id, name: a.name, status: a.status, task: a.task || a.type || '', type: a.type || 'main' }; order.push(a.id); });
|
|
263
|
+
var sess = (d.session && d.session.agents) || [];
|
|
264
|
+
sess.forEach(function (a) { var e = out[a.id] || { id: a.id, name: a.name, status: a.status, task: a.task, type: a.type }; e.tokensIn = a.tokensIn; e.tokensOut = a.tokensOut; e.tokensUsed = a.tokensUsed; e.model = a.model; e.cost = a.cost; e.durationMs = a.durationMs; e.taskCount = a.taskCount; if (!out[a.id]) order.push(a.id); out[a.id] = e; });
|
|
265
|
+
return order.map(function (id) { return out[id]; });
|
|
266
|
+
}
|
|
267
|
+
function agentDrawer(a, d) {
|
|
268
|
+
var body = el('div', {});
|
|
269
|
+
body.appendChild(kv('ID', a.id, 'cyan'));
|
|
270
|
+
body.appendChild(kv('Name', a.name || '-'));
|
|
271
|
+
body.appendChild(kv('Type', a.type || 'main'));
|
|
272
|
+
body.appendChild(kv('Status', a.status || '-'));
|
|
273
|
+
body.appendChild(kv('Task', a.task || '-'));
|
|
274
|
+
if (a.model) body.appendChild(kv('Model', modelName(a.model), 'purple'));
|
|
275
|
+
body.appendChild(el('h3', {}, 'Tokens & cost'));
|
|
276
|
+
body.appendChild(kv('Tokens In', fmtNum(a.tokensIn || 0), 'cyan'));
|
|
277
|
+
body.appendChild(kv('Tokens Out', fmtNum(a.tokensOut || 0), 'green'));
|
|
278
|
+
body.appendChild(kv('Total', fmtNum((a.tokensIn || 0) + (a.tokensOut || 0) || a.tokensUsed || 0), 'purple'));
|
|
279
|
+
body.appendChild(kv('Cost', fmtUsd(a.cost || 0), 'green'));
|
|
280
|
+
if (a.taskCount != null) body.appendChild(kv('Tasks', String(a.taskCount)));
|
|
281
|
+
if (a.durationMs) body.appendChild(kv('Duration', U.fmtDur(a.durationMs)));
|
|
282
|
+
var skills = (d.coordination && d.coordination.skillsPerAgent && d.coordination.skillsPerAgent[a.id]) || [];
|
|
283
|
+
if (skills.length) { body.appendChild(el('h3', {}, 'Skills')); var sc = el('div', { class: 'chip-list' }); skills.forEach(function (s) { sc.appendChild(el('span', { class: 'chip', text: s })); }); body.appendChild(sc); }
|
|
284
|
+
var pats = (d.coordination && d.coordination.patternsPerAgent && d.coordination.patternsPerAgent[a.id]) || [];
|
|
285
|
+
if (pats.length) { body.appendChild(el('h3', {}, 'Patterns')); var pc = el('div', { class: 'chip-list' }); pats.forEach(function (p) { pc.appendChild(el('span', { class: 'chip', text: (p.id || p) + (p.uses ? ' ×' + p.uses : '') })); }); body.appendChild(pc); }
|
|
286
|
+
body.appendChild(el('h3', {}, 'Actions'));
|
|
287
|
+
body.appendChild(el('div', { class: 'toolbar' }, el('button', { class: 'btn btn-danger', onclick: function () {
|
|
288
|
+
U.confirm('Deregister agent ' + (a.name || a.id) + '? Marks its registry row completed and releases its claims (does not kill the OS process).', { danger: true, okLabel: 'Deregister' }).then(function (ok) { if (ok) U.api('/api/agents/' + encodeURIComponent(a.id) + '/deregister', {}).then(function () { U.toast('Agent deregistered', 'ok'); U.closeDrawer(); }); });
|
|
289
|
+
} }, 'Deregister')));
|
|
290
|
+
U.drawer('Agent: ' + (a.name || a.id).slice(0, 40), body);
|
|
291
|
+
}
|
|
292
|
+
function sessionDrawer(s, d) {
|
|
293
|
+
var body = el('div', {});
|
|
294
|
+
var isLive = d.session && d.session.sessionId === s.sessionId;
|
|
295
|
+
var src = isLive ? d.session : s;
|
|
296
|
+
body.appendChild(kv('Session', s.sessionId, 'cyan'));
|
|
297
|
+
body.appendChild(kv('Status', s.status || '-'));
|
|
298
|
+
body.appendChild(kv('Model', modelName(s.model || (src.modelBreakdown && src.modelBreakdown[0] && src.modelBreakdown[0].modelId) || '-'), 'purple'));
|
|
299
|
+
body.appendChild(kv('Tokens In', fmtNum(s.tokensIn || src.tokensIn || 0), 'cyan'));
|
|
300
|
+
body.appendChild(kv('Tokens Out', fmtNum(s.tokensOut || src.tokensOut || 0), 'green'));
|
|
301
|
+
body.appendChild(kv('Cost', fmtUsd(s.totalCost != null ? s.totalCost : src.totalCostUsd || 0), 'green'));
|
|
302
|
+
body.appendChild(kv('Agents', String(s.agentCount != null ? s.agentCount : (src.agents ? src.agents.length : 0))));
|
|
303
|
+
body.appendChild(kv('Tasks', String(s.taskCount || 0)));
|
|
304
|
+
if (isLive && src.agents && src.agents.length) {
|
|
305
|
+
body.appendChild(el('h3', {}, 'Agents (live session)'));
|
|
306
|
+
body.appendChild(tableNode(['Agent', 'Model', 'In', 'Out', 'Cost'], src.agents, function (a) { return [a.name || a.id, modelName(a.model || '?'), fmtNum(a.tokensIn || 0), fmtNum(a.tokensOut || 0), fmtUsd(a.cost || 0)]; }));
|
|
307
|
+
}
|
|
308
|
+
U.drawer('Session: ' + (s.sessionId || '').slice(0, 20), body);
|
|
309
|
+
}
|
|
310
|
+
function build(root, d) {
|
|
311
|
+
var agents = mergeAgents(d);
|
|
312
|
+
var ap = panel('Agents', [el('button', { class: 'btn', onclick: function () { U.confirm('Clean up stale agents (stale heartbeats)?').then(function (ok) { if (ok) U.api('/api/agents/clean', {}).then(function (r) { U.toast('Cleaned ' + ((r && r.cleaned) || 0) + ' stale agents', 'ok'); }); }); } }, 'Clean stale')]);
|
|
313
|
+
if (!agents.length) ap.appendChild(empty('No agents registered'));
|
|
314
|
+
else { var grid = el('div', { class: 'card-grid' }); agents.forEach(function (a) {
|
|
315
|
+
grid.appendChild(el('div', { class: 'entity-card', tabindex: '0', role: 'button', onclick: function () { agentDrawer(a, d); }, onkeydown: function (e) { if (e.key === 'Enter') agentDrawer(a, d); } },
|
|
316
|
+
el('div', { class: 'ec-head' }, el('span', { class: 'badge ' + (a.type || 'main') }, a.type || 'agent'), el('span', { class: 'ec-name', text: a.name || a.id }), statusBadge(a.status)),
|
|
317
|
+
el('div', { class: 'ec-task', text: a.task || '-' }),
|
|
318
|
+
el('div', { class: 'ec-metrics' }, el('span', {}, fmtNum((a.tokensIn || 0) + (a.tokensOut || 0) || a.tokensUsed || 0) + ' tok'), a.model ? el('span', { class: 'muted', text: modelName(a.model) }) : null, el('span', {}, fmtUsd(a.cost || 0)))));
|
|
319
|
+
}); ap.appendChild(grid); }
|
|
320
|
+
root.appendChild(ap);
|
|
321
|
+
|
|
322
|
+
var sessions = d.sessions || [];
|
|
323
|
+
var sp = panel('Session History');
|
|
324
|
+
sp.appendChild(sessions.length ? tableNode(['Session', 'Status', 'Model', 'In', 'Out', 'Cost', 'Agents', 'Tasks'], sessions, function (s) {
|
|
325
|
+
return [el('span', {}, (s.sessionId || '-').slice(0, 14), s.status === 'active' ? el('span', { class: 'mono-sm', text: ' (live)' }) : null), statusBadge(s.status), modelName(s.model || '-'), fmtNum(s.tokensIn || 0), fmtNum(s.tokensOut || 0), fmtUsd(s.totalCost || 0), String(s.agentCount || 0), String(s.taskCount || 0)];
|
|
326
|
+
}, function (s) { sessionDrawer(s, d); }) : empty('No session history'));
|
|
327
|
+
root.appendChild(sp);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
label: 'Agents & Sessions',
|
|
331
|
+
render: function (root, d) { build(root, d); },
|
|
332
|
+
update: function (root, d) { var s = sig({ a: (d.coordination || {}).agents, se: (d.session || {}).agents, h: d.sessions }); if (s !== lastSig) { lastSig = s; U.clear(root); build(root, d); } },
|
|
333
|
+
};
|
|
334
|
+
})());
|
|
335
|
+
|
|
336
|
+
// ═══════════════════════════ ORCHESTRATION ═══════════════════════════
|
|
337
|
+
U.registerTab('orchestration', (function () {
|
|
338
|
+
function node(n, depth) {
|
|
339
|
+
var sc = { done: 'green', in_progress: 'cyan', blocked: 'red', failed: 'red', open: '', pending: '' }[n.status] || '';
|
|
340
|
+
var icon = n.type === 'epic' ? '🎯' : n.type === 'feature' ? '✨' : n.type === 'phase' ? '▶' : '•';
|
|
341
|
+
var agents = (n.agents && n.agents.length) ? el('span', { class: 'mono-sm', text: ' [' + n.agents.join(', ') + ']' }) : null;
|
|
342
|
+
var line = el('div', { style: { padding: '3px 0 3px ' + (depth * 16) + 'px', borderLeft: '1px solid var(--border)' } },
|
|
343
|
+
el('span', { class: 'value ' + sc, text: '●' }), ' ' + icon + ' ', el('span', { text: (n.title || n.id).slice(0, 70) }), el('span', { class: 'mono-sm', text: ' ' + (n.status || '') }), agents);
|
|
344
|
+
var wrap = el('div', {}, line);
|
|
345
|
+
(n.children || []).forEach(function (c) { wrap.appendChild(node(c, depth + 1)); });
|
|
346
|
+
return wrap;
|
|
347
|
+
}
|
|
348
|
+
function build(root, d) {
|
|
349
|
+
var ot = d.orchestrationTree || { missions: [], ledger: null, agents: [], hasHierarchy: false };
|
|
350
|
+
var cur = d.orchestrate || 'auto';
|
|
351
|
+
var p = panel('Orchestrator');
|
|
352
|
+
var ctl = el('div', { class: 'toolbar' }, el('span', { class: 'label', text: 'Mode:' }));
|
|
353
|
+
['on', 'off', 'auto'].forEach(function (st) { ctl.appendChild(el('button', { class: 'btn' + (st === cur ? ' btn-primary' : ''), onclick: function () { U.api('/api/orchestrator', { state: st }).then(function () { U.toast('Orchestrator: ' + st, 'ok'); }); } }, st)); });
|
|
354
|
+
p.appendChild(ctl);
|
|
355
|
+
p.appendChild(el('div', { class: 'section-note', text: (ot.missions || []).length + ' root mission(s) · ' + (ot.agents || []).length + ' agent(s)' }));
|
|
356
|
+
root.appendChild(p);
|
|
357
|
+
|
|
358
|
+
var led = ot.ledger;
|
|
359
|
+
if (led && led.total) {
|
|
360
|
+
var lp = panel('Active Build Ledger');
|
|
361
|
+
var pct = led.pct != null ? led.pct : Math.round((led.done / led.total) * 100);
|
|
362
|
+
lp.appendChild(el('div', { class: 'kv' }, el('span', { class: 'label', text: (led.mission || '-').slice(0, 70) }), el('span', { class: 'value cyan', text: led.done + '/' + led.total + ' (' + pct + '%)' })));
|
|
363
|
+
lp.appendChild(el('div', { class: 'progress-track' }, el('div', { class: 'progress-fill', style: { width: pct + '%' } })));
|
|
364
|
+
lp.appendChild(ledgerItems(led));
|
|
365
|
+
lp.appendChild(el('div', { class: 'toolbar' }, el('button', { class: 'btn btn-danger', onclick: function () { U.confirm('Reset the completion ledger? Clears the active multi-epic build state.', { danger: true, okLabel: 'Reset' }).then(function (ok) { if (ok) U.api('/api/ledger/reset', {}).then(function () { U.toast('Ledger reset', 'ok'); }); }); } }, 'Reset ledger')));
|
|
366
|
+
root.appendChild(lp);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
var tp = panel('Mission Hierarchy');
|
|
370
|
+
var withKids = (ot.missions || []).filter(function (m) { return (m.children || []).length > 0; });
|
|
371
|
+
var shown = (withKids.length ? withKids : (ot.missions || [])).slice(0, 30);
|
|
372
|
+
if (!shown.length) tp.appendChild(empty('No orchestrations yet — run an epic/orchestrated build'));
|
|
373
|
+
else shown.forEach(function (m) { tp.appendChild(node(m, 0)); });
|
|
374
|
+
root.appendChild(tp);
|
|
375
|
+
}
|
|
376
|
+
return { label: 'Orchestration', render: build };
|
|
377
|
+
})());
|
|
378
|
+
|
|
379
|
+
// ═══════════════════════════ DELIVER ═══════════════════════════
|
|
380
|
+
U.registerTab('deliver', (function () {
|
|
381
|
+
function runDrawer(r) {
|
|
382
|
+
var body = el('div', {});
|
|
383
|
+
body.appendChild(kv('Run ID', r.runId, 'cyan'));
|
|
384
|
+
body.appendChild(kv('Status', r.status || '-'));
|
|
385
|
+
if (r.instruction || r.mission) { body.appendChild(el('h3', {}, 'Instruction')); body.appendChild(el('div', { class: 'muted', text: r.instruction || r.mission })); }
|
|
386
|
+
var _ph = runPhase(r); if (_ph !== '-') body.appendChild(kv('Phase', _ph));
|
|
387
|
+
if (r.taskId) body.appendChild(kv('Task', r.taskId));
|
|
388
|
+
if (r.pid) body.appendChild(kv('PID', String(r.pid)));
|
|
389
|
+
body.appendChild(kv('Started', U.shortTime(r.createdAt)));
|
|
390
|
+
body.appendChild(kv('Updated', U.timeAgo(r.updatedAt || r.createdAt)));
|
|
391
|
+
if (Array.isArray(r.phases) && r.phases.length) { body.appendChild(el('h3', {}, 'Phases')); r.phases.forEach(function (p, i) { body.appendChild(kv((i + 1) + '. ' + (p.title || p.id || '-'), i === (r.phaseIndex || 0) ? 'active' : '')); }); }
|
|
392
|
+
body.appendChild(el('h3', {}, 'Actions'));
|
|
393
|
+
var actions = el('div', { class: 'toolbar' });
|
|
394
|
+
if (r.status === 'running') actions.appendChild(el('button', { class: 'btn btn-danger', onclick: function () { U.confirm('Cancel deliver run ' + r.runId + '? Writes a stop-file (cooperative) and signals the process.', { danger: true, okLabel: 'Cancel run' }).then(function (ok) { if (ok) U.api('/api/deliver/' + encodeURIComponent(r.runId) + '/cancel', {}).then(function () { U.toast('Cancel requested', 'ok'); U.closeDrawer(); }); }); } }, 'Cancel'));
|
|
395
|
+
if (r.status === 'interrupted' || r.status === 'failed') actions.appendChild(el('button', { class: 'btn btn-primary', onclick: function () { U.api('/api/deliver/' + encodeURIComponent(r.runId) + '/resume', {}).then(function () { U.toast('Resume launched', 'ok'); U.closeDrawer(); }); } }, 'Resume'));
|
|
396
|
+
body.appendChild(actions);
|
|
397
|
+
U.drawer('Deliver run ' + (r.runId || '').slice(0, 16), body);
|
|
398
|
+
}
|
|
399
|
+
function build(root, d) {
|
|
400
|
+
var runs = d.deliverRuns || [];
|
|
401
|
+
var p = panel('Deliver Runs', [el('button', { class: 'btn btn-primary', onclick: function () {
|
|
402
|
+
U.form('Launch deliver run', [
|
|
403
|
+
{ name: 'instruction', label: 'Instruction', type: 'textarea', placeholder: 'One-line description of what to build' },
|
|
404
|
+
{ name: 'model', label: 'Model preset (optional)', placeholder: 'qwen35-a3b' },
|
|
405
|
+
{ name: 'maxTurns', label: 'Max turns', type: 'number', value: '5' },
|
|
406
|
+
]).then(function (v) {
|
|
407
|
+
if (!v || !v.instruction) return;
|
|
408
|
+
U.confirm('Launch a deliver run on this host? This spawns a `uap deliver` subprocess that writes files and runs gates.', { okLabel: 'Launch' }).then(function (ok) {
|
|
409
|
+
if (ok) U.api('/api/deliver/launch', { instruction: v.instruction, model: v.model || undefined, maxTurns: Number(v.maxTurns) || undefined }).then(function (res) { U.toast('Launched' + (res && res.runId ? ' (' + String(res.runId).slice(0, 10) + ')' : ''), 'ok'); });
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
} }, '▶ Launch run')]);
|
|
413
|
+
p.appendChild(el('div', { class: 'section-note', text: 'Runs are read from .uap/deliver-runs. Launch spawns a host process; cancel writes a cooperative stop-file and signals the PID.' }));
|
|
414
|
+
p.appendChild(runs.length ? tableNode(['Run', 'Status', 'Phase', 'Task', 'Updated', ''], runs, function (r) {
|
|
415
|
+
var act = el('span', {});
|
|
416
|
+
if (r.status === 'running') act.appendChild(el('button', { class: 'btn btn-danger', onclick: function (e) { e.stopPropagation(); U.confirm('Cancel run ' + r.runId + '?', { danger: true, okLabel: 'Cancel run' }).then(function (ok) { if (ok) U.api('/api/deliver/' + encodeURIComponent(r.runId) + '/cancel', {}).then(function () { U.toast('Cancel requested', 'ok'); }); }); } }, 'Cancel'));
|
|
417
|
+
else if (r.status === 'interrupted' || r.status === 'failed') act.appendChild(el('button', { class: 'btn', onclick: function (e) { e.stopPropagation(); U.api('/api/deliver/' + encodeURIComponent(r.runId) + '/resume', {}).then(function () { U.toast('Resume launched', 'ok'); }); } }, 'Resume'));
|
|
418
|
+
return [el('span', { class: 'mono-sm', text: (r.runId || '').slice(0, 12) }), statusBadge(r.status), runPhase(r), el('span', { class: 'mono-sm', text: r.taskId || '-' }), U.timeAgo(r.updatedAt || r.createdAt), act];
|
|
419
|
+
}, function (r) { runDrawer(r); }) : empty('No deliver runs tracked yet'));
|
|
420
|
+
root.appendChild(p);
|
|
421
|
+
}
|
|
422
|
+
return { label: 'Deliver', render: build };
|
|
423
|
+
})());
|
|
424
|
+
|
|
425
|
+
// ═══════════════════════════ POLICIES & COMPLIANCE ═══════════════════════════
|
|
426
|
+
U.registerTab('policies', (function () {
|
|
427
|
+
function build(root, d) {
|
|
428
|
+
var policies = d.policies || [], policyFiles = d.policyFiles || [], compliance = d.compliance || {}, audit = d.auditTrail || [];
|
|
429
|
+
var pm = {}, order = [];
|
|
430
|
+
policies.forEach(function (p) { var key = p.name || p.id; pm[key] = Object.assign({}, p, { source: 'db' }); order.push(key); });
|
|
431
|
+
policyFiles.forEach(function (pf) { if (!pm[pf.name]) { pm[pf.name] = { id: pf.filename, name: pf.name, category: pf.category, level: '-', enforcementStage: '-', isActive: null, source: 'file' }; order.push(pf.name); } });
|
|
432
|
+
var all = order.map(function (k) { return pm[k]; });
|
|
433
|
+
|
|
434
|
+
var pp = panel('Policies');
|
|
435
|
+
var thead = el('tr', {}); ['Name', 'Category', 'Level', 'Stage', 'Status', 'Actions'].forEach(function (x) { thead.appendChild(el('th', { text: x })); });
|
|
436
|
+
var tbl = el('table', {}, thead);
|
|
437
|
+
all.forEach(function (p) {
|
|
438
|
+
var isDb = p.source === 'db';
|
|
439
|
+
var actions = el('span', {});
|
|
440
|
+
if (isDb) {
|
|
441
|
+
actions.appendChild(el('button', { class: 'btn', onclick: function () { U.api('/api/policy/' + encodeURIComponent(p.id) + '/toggle', undefined).then(function (r) { U.toast('Policy ' + (r.isActive ? 'enabled' : 'disabled'), 'ok'); }); } }, p.isActive ? 'Disable' : 'Enable'));
|
|
442
|
+
var stageSel = el('select', { onchange: function (e) { U.api('/api/policy/' + encodeURIComponent(p.id) + '/stage', { stage: e.target.value }).then(function () { U.toast('Stage: ' + e.target.value, 'ok'); }); } });
|
|
443
|
+
['pre-exec', 'post-exec', 'review', 'always'].forEach(function (s) { stageSel.appendChild(el('option', { value: s }, s)); }); stageSel.value = p.enforcementStage;
|
|
444
|
+
var lvlSel = el('select', { onchange: function (e) { U.api('/api/policy/' + encodeURIComponent(p.id) + '/level', { level: e.target.value }).then(function () { U.toast('Level: ' + e.target.value, 'ok'); }); } });
|
|
445
|
+
['REQUIRED', 'RECOMMENDED', 'OPTIONAL'].forEach(function (l) { lvlSel.appendChild(el('option', { value: l }, l)); }); lvlSel.value = p.level;
|
|
446
|
+
actions.appendChild(document.createTextNode(' ')); actions.appendChild(stageSel); actions.appendChild(document.createTextNode(' ')); actions.appendChild(lvlSel);
|
|
447
|
+
} else actions.appendChild(el('span', { class: 'mono-sm', text: 'file-only' }));
|
|
448
|
+
var status = isDb ? el('span', { class: 'badge ' + (p.isActive ? 'on' : 'off') }, p.isActive ? 'ON' : 'OFF') : el('span', { class: 'badge file-only' }, 'FILE');
|
|
449
|
+
var tr = el('tr', {},
|
|
450
|
+
el('td', {}, el('span', { style: { fontWeight: '500' }, text: p.name || '-' })),
|
|
451
|
+
el('td', { text: p.category || '-' }),
|
|
452
|
+
el('td', {}, el('span', { class: 'badge ' + (p.level || '').toLowerCase() }, p.level || '-')),
|
|
453
|
+
el('td', { text: p.enforcementStage || '-' }),
|
|
454
|
+
el('td', {}, status),
|
|
455
|
+
el('td', {}, actions));
|
|
456
|
+
tbl.appendChild(tr);
|
|
457
|
+
});
|
|
458
|
+
pp.appendChild(all.length ? el('div', { class: 'table-wrap' }, tbl) : empty('No policies'));
|
|
459
|
+
root.appendChild(pp);
|
|
460
|
+
|
|
461
|
+
var cp = panel('Compliance & Audit');
|
|
462
|
+
cp.appendChild(el('h3', {}, 'Block Rate Trend'));
|
|
463
|
+
cp.appendChild(el('div', { class: 'chart-container chart-spark', id: 'pol-chart-block' }));
|
|
464
|
+
cp.appendChild(el('h3', {}, 'Failures by Mechanism'));
|
|
465
|
+
var fbm = compliance.failuresByMechanism || {}; var me = Object.keys(fbm).map(function (k) { return [k, fbm[k]]; }).sort(function (a, b) { return b[1] - a[1]; });
|
|
466
|
+
if (me.length) { var max = Math.max.apply(null, me.map(function (x) { return x[1]; }).concat([1])); var mb = el('div', {}); me.forEach(function (m) { mb.appendChild(el('div', { class: 'bar-container' }, el('span', { class: 'bar-label', style: { width: '120px' }, text: m[0] }), el('div', { class: 'bar red', style: { width: Math.round((m[1] / max) * 180) + 'px' } }), el('span', { class: 'bar-value', text: String(m[1]) }))); }); cp.appendChild(mb); }
|
|
467
|
+
else cp.appendChild(empty('No failure mechanisms'));
|
|
468
|
+
cp.appendChild(el('h3', {}, 'Recent Failures'));
|
|
469
|
+
var rf = compliance.recentFailures || [];
|
|
470
|
+
cp.appendChild(rf.length ? tableNode(['Time', 'Policy', 'Op', 'Mechanism', 'Reason'], rf.slice(0, 10), function (f) { return [el('span', { class: 'mono-sm', text: (f.executedAt || '').slice(11, 19) }), el('span', { class: 'value red', text: f.policyName || f.policyId || '-' }), f.operation || '-', el('span', { class: 'badge required' }, f.defeatedMechanism || '-'), el('span', { class: 'muted', text: (f.reason || '-').slice(0, 60) })]; }) : empty('No recent failures'));
|
|
471
|
+
cp.appendChild(el('h3', {}, 'Audit Trail'));
|
|
472
|
+
var at = el('div', {});
|
|
473
|
+
if (audit.length) audit.slice(0, 15).forEach(function (e) { var ts = (typeof e.executedAt === 'string' && e.executedAt.length >= 19) ? e.executedAt.slice(11, 19) : (e.executedAt || '-'); at.appendChild(el('div', { class: 'audit-row' }, el('span', { class: 'audit-time', text: ts }), el('span', { class: 'audit-icon ' + (e.allowed ? 'pass' : 'block'), text: e.allowed ? 'PASS' : 'BLOCK' }), el('span', { class: 'audit-policy', text: (e.policyId || '').slice(0, 8) }), el('span', { class: 'audit-op', text: e.operation || '' }), el('span', { class: 'audit-reason', text: e.reason || '' }))); });
|
|
474
|
+
else at.appendChild(empty('No audit entries'));
|
|
475
|
+
cp.appendChild(at);
|
|
476
|
+
cp.appendChild(el('h3', {}, 'Live Events'));
|
|
477
|
+
var feed = el('div', { class: 'event-feed', id: 'pol-live-events' });
|
|
478
|
+
fillEvents(feed);
|
|
479
|
+
cp.appendChild(feed);
|
|
480
|
+
root.appendChild(cp);
|
|
481
|
+
U.charts.syncSpark('pol-chart-block', d.timeSeries || [], U.charts.parseBR, U.charts.CC.blockRate, 'Block Rate %');
|
|
482
|
+
}
|
|
483
|
+
function fillEvents(feed) {
|
|
484
|
+
U.clear(feed);
|
|
485
|
+
var ev = (U.liveEvents || []).slice(0, 20);
|
|
486
|
+
if (!ev.length) { feed.appendChild(empty('Waiting for events…')); return; }
|
|
487
|
+
ev.forEach(function (e) { feed.appendChild(eventRow(e)); });
|
|
488
|
+
}
|
|
489
|
+
U.onEvents(function () { var f = document.getElementById('pol-live-events'); if (f) fillEvents(f); });
|
|
490
|
+
return { label: 'Policies', render: build };
|
|
491
|
+
})());
|
|
492
|
+
|
|
493
|
+
// ═══════════════════════════ MODELS ═══════════════════════════
|
|
494
|
+
U.registerTab('models', (function () {
|
|
495
|
+
function build(root, d) {
|
|
496
|
+
var m = d.models || { roles: {}, sessionUsage: [], totalCost: 0, strategy: 'unknown' };
|
|
497
|
+
var p = panel('Models & Routing');
|
|
498
|
+
p.appendChild(el('h3', {}, 'Roles'));
|
|
499
|
+
var roles = m.roles || {}; var rk = Object.keys(roles);
|
|
500
|
+
if (rk.length) rk.forEach(function (role) { var c = role === 'planner' ? 'green' : role === 'executor' ? 'cyan' : role === 'reviewer' ? 'purple' : ''; p.appendChild(kv(U.capitalize(role), roles[role] ? modelName(String(roles[role])) : '—', c)); });
|
|
501
|
+
else p.appendChild(empty('No roles configured'));
|
|
502
|
+
p.appendChild(el('h3', {}, 'Router'));
|
|
503
|
+
p.appendChild(kv('Strategy', m.strategy || 'unknown', 'cyan'));
|
|
504
|
+
p.appendChild(kv('Available', String((m.availableModels || []).length)));
|
|
505
|
+
p.appendChild(kv('Enabled', m.enabled ? 'Yes' : 'No', m.enabled ? 'green' : 'yellow'));
|
|
506
|
+
p.appendChild(kv('Total Cost', fmtUsd(m.totalCost || 0), 'green'));
|
|
507
|
+
var co = m.costOptimization || {};
|
|
508
|
+
p.appendChild(el('h3', {}, 'Cost Optimization'));
|
|
509
|
+
p.appendChild(kv('Enabled', co.enabled ? 'Yes' : 'No', co.enabled ? 'green' : 'yellow'));
|
|
510
|
+
p.appendChild(kv('Target Reduction', co.targetReduction ? co.targetReduction.toFixed(0) + '%' : 'N/A'));
|
|
511
|
+
p.appendChild(kv('Max Degradation', co.maxPerformanceDegradation ? co.maxPerformanceDegradation.toFixed(0) + '%' : 'N/A'));
|
|
512
|
+
root.appendChild(p);
|
|
513
|
+
|
|
514
|
+
var mx = m.routingMatrix || {}; var mk = Object.keys(mx);
|
|
515
|
+
var rp = panel('Routing Matrix');
|
|
516
|
+
if (mk.length) rp.appendChild(tableNode(['Task Type', 'Planner', 'Executor'], mk, function (k) { var v = mx[k]; var isObj = v && typeof v === 'object'; return [k, el('span', { class: 'value green', text: isObj ? modelName(v.planner || '-') : '-' }), el('span', { class: 'value cyan', text: isObj ? modelName(v.executor || '-') : modelName(String(v)) })]; }));
|
|
517
|
+
else rp.appendChild(empty('No routing matrix'));
|
|
518
|
+
root.appendChild(rp);
|
|
519
|
+
|
|
520
|
+
var rd = m.recentRoutingDecisions || [];
|
|
521
|
+
var dp = panel('Recent Routing Decisions');
|
|
522
|
+
dp.appendChild(rd.length ? tableNode(['Time', 'Model', 'Task', 'Tokens', 'Cost', 'Result'], rd.slice(0, 12), function (r) { return [el('span', { class: 'mono-sm', text: (r.timestamp || '').slice(11, 19) }), el('span', { class: 'value purple', text: modelName(r.modelUsed) }), r.taskType || '?', fmtNum((r.tokensIn || 0) + (r.tokensOut || 0)), el('span', { class: 'value green', text: fmtUsd(r.cost || 0) }), el('span', { class: 'value ' + (r.success ? 'green' : 'red'), text: r.success ? 'OK' : 'FAIL' })]; }) : empty('No routing decisions yet'));
|
|
523
|
+
root.appendChild(dp);
|
|
524
|
+
|
|
525
|
+
var usage = m.sessionUsage || [];
|
|
526
|
+
var up = panel('Session Usage by Model');
|
|
527
|
+
up.appendChild(usage.length ? tableNode(['Model', 'Tasks', 'In', 'Out', 'Cost', 'Success'], usage, function (u) { var rate = typeof u.successRate === 'number' ? (u.successRate * 100).toFixed(0) : '0'; return [el('span', { class: 'value cyan', text: modelName(u.modelId) }), String(u.taskCount || 0), fmtNum(u.totalTokensIn || 0), fmtNum(u.totalTokensOut || 0), fmtUsd(u.totalCost || 0), el('span', { class: 'value ' + (rate >= 90 ? 'green' : rate >= 70 ? 'yellow' : 'red'), text: rate + '%' })]; }) : empty('No usage recorded'));
|
|
528
|
+
root.appendChild(up);
|
|
529
|
+
}
|
|
530
|
+
return { label: 'Models', render: build };
|
|
531
|
+
})());
|
|
532
|
+
|
|
533
|
+
// ═══════════════════════════ MEMORY ═══════════════════════════
|
|
534
|
+
U.registerTab('memory', (function () {
|
|
535
|
+
function build(root, d) {
|
|
536
|
+
var mem = d.memory || {}, l1 = mem.l1 || {}, l2 = mem.l2 || {}, l3 = mem.l3 || {}, l4 = mem.l4 || {}, comp = mem.compression || {}, hm = mem.hitsMisses || {};
|
|
537
|
+
var g = el('div', { class: 'grid-2' });
|
|
538
|
+
var lp = panel('Memory Tiers');
|
|
539
|
+
lp.appendChild(kv('L1 Working', (l1.entries || 0) + ' entries (' + (l1.sizeKB || 0) + ' KB)'));
|
|
540
|
+
lp.appendChild(kv('L2 Session', (l2.entries || 0) + ' entries'));
|
|
541
|
+
lp.appendChild(kv('L3 Semantic', l3.status === 'Running' ? 'Qdrant ' + (l3.uptime || '') : (l3.status || 'Stopped'), l3.status === 'Running' ? 'green' : 'yellow'));
|
|
542
|
+
lp.appendChild(kv('L4 Knowledge', (l4.entities || 0) + ' entities, ' + (l4.relationships || 0) + ' rels'));
|
|
543
|
+
lp.appendChild(el('h3', {}, 'Hit Rate'));
|
|
544
|
+
var hrStr = String(hm.hitRate || 'N/A'); var hrVal = parseFloat(hrStr) || 0;
|
|
545
|
+
lp.appendChild(kv('Hits', String(hm.hits || 0), 'green'));
|
|
546
|
+
lp.appendChild(kv('Misses', String(hm.misses || 0), 'red'));
|
|
547
|
+
lp.appendChild(kv('Rate', hrStr.indexOf('%') >= 0 ? hrStr : (hrStr === 'N/A' ? hrStr : hrStr + '%'), hrVal > 80 ? 'green' : hrVal > 50 ? 'yellow' : 'red'));
|
|
548
|
+
lp.appendChild(el('div', { class: 'gauge-track' }, el('div', { class: 'gauge-fill', style: { width: Math.min(hrVal, 100) + '%', background: hrVal > 80 ? 'var(--green)' : hrVal > 50 ? 'var(--yellow)' : 'var(--red)' } })));
|
|
549
|
+
lp.appendChild(el('div', { class: 'chart-container chart-spark', id: 'mem-chart-hit' }));
|
|
550
|
+
g.appendChild(lp);
|
|
551
|
+
|
|
552
|
+
var cp = panel('Context Compression');
|
|
553
|
+
cp.appendChild(kv('Raw', fmtKB(comp.rawBytes || 0)));
|
|
554
|
+
cp.appendChild(kv('Compressed', fmtKB(comp.contextBytes || 0)));
|
|
555
|
+
cp.appendChild(kv('Savings', comp.savingsPercent || '0%', 'green'));
|
|
556
|
+
cp.appendChild(kv('Tool Calls', String(comp.totalCalls || 0)));
|
|
557
|
+
cp.appendChild(el('h3', {}, 'Recent Queries'));
|
|
558
|
+
var rq = mem.recentQueries || [];
|
|
559
|
+
cp.appendChild(rq.length ? tableNode(['Type', 'Query', 'Time'], rq.slice(0, 10), function (q) { return [el('span', { class: 'badge active' }, q.type || 'memory'), el('span', { class: 'muted', text: (q.query || '-').slice(0, 40) }), el('span', { class: 'mono-sm', text: (q.timestamp || '').slice(11, 19) })]; }) : empty('No recent queries'));
|
|
560
|
+
g.appendChild(cp);
|
|
561
|
+
root.appendChild(g);
|
|
562
|
+
|
|
563
|
+
var sv = d.savingsByInfluence || { influences: [], totalTokensSaved: 0, totalCostSavedUsd: 0 };
|
|
564
|
+
var sp = panel('Token Savings by Influence');
|
|
565
|
+
sp.appendChild(el('div', { style: { marginBottom: '12px', fontSize: '14px' } }, el('strong', { class: 'value cyan', style: { fontSize: '18px' }, text: fmtNum(sv.totalTokensSaved || 0) }), ' tokens · ', el('strong', { class: 'value green', style: { fontSize: '18px' }, text: fmtUsd(sv.totalCostSavedUsd || 0) }), ' saved across all UAP influences'));
|
|
566
|
+
sp.appendChild((sv.influences || []).length ? tableNode(['Influence', 'Tokens saved', 'Cost saved', 'Quality', 'Detail'], sv.influences, function (i) {
|
|
567
|
+
var idle = i.quality === 'unmeasured';
|
|
568
|
+
var tok = idle ? el('span', { class: 'muted', text: '—' }) : fmtNum(i.tokensSaved);
|
|
569
|
+
var cost = idle ? el('span', { class: 'muted', text: '—' }) : el('span', { class: 'value green', text: fmtUsd(i.costSavedUsd || 0) });
|
|
570
|
+
var qc = i.quality === 'measured' ? 'green' : i.quality === 'estimated' ? 'yellow' : '';
|
|
571
|
+
return [i.influence, tok, cost, el('span', { class: 'value ' + qc, text: i.quality }), el('span', { class: 'muted', text: i.detail || '' })];
|
|
572
|
+
}) : empty('No savings data'));
|
|
573
|
+
root.appendChild(sp);
|
|
574
|
+
U.charts.syncSpark('mem-chart-hit', d.timeSeries || [], U.charts.parseHR, U.charts.CC.hitRate, 'Hit Rate %');
|
|
575
|
+
}
|
|
576
|
+
return { label: 'Memory', render: build };
|
|
577
|
+
})());
|
|
578
|
+
|
|
579
|
+
// ── boot ──
|
|
580
|
+
U.start();
|
|
581
|
+
})();
|