@miller-tech/uap 1.134.0 → 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.
Files changed (35) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cli/deliver.js +11 -2
  3. package/dist/cli/deliver.js.map +1 -1
  4. package/dist/dashboard/controls.d.ts +66 -0
  5. package/dist/dashboard/controls.d.ts.map +1 -0
  6. package/dist/dashboard/controls.js +188 -0
  7. package/dist/dashboard/controls.js.map +1 -0
  8. package/dist/dashboard/data-service.d.ts +20 -0
  9. package/dist/dashboard/data-service.d.ts.map +1 -1
  10. package/dist/dashboard/data-service.js +47 -0
  11. package/dist/dashboard/data-service.js.map +1 -1
  12. package/dist/dashboard/server.d.ts.map +1 -1
  13. package/dist/dashboard/server.js +81 -5
  14. package/dist/dashboard/server.js.map +1 -1
  15. package/dist/delivery/convergence-loop.d.ts +5 -0
  16. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  17. package/dist/delivery/convergence-loop.js +11 -0
  18. package/dist/delivery/convergence-loop.js.map +1 -1
  19. package/dist/delivery/run-state.d.ts +12 -0
  20. package/dist/delivery/run-state.d.ts.map +1 -1
  21. package/dist/delivery/run-state.js +58 -1
  22. package/dist/delivery/run-state.js.map +1 -1
  23. package/docs/DASHBOARD_UPLIFT_SPEC.md +171 -0
  24. package/package.json +1 -1
  25. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  26. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  27. package/tools/agents/scripts/anthropic_proxy.py +27 -0
  28. package/web/dash/core.js +398 -0
  29. package/web/dash/styles.css +307 -0
  30. package/web/dash/tab-deliver.js +12 -0
  31. package/web/dash/tab-memory.js +12 -0
  32. package/web/dash/tab-models.js +12 -0
  33. package/web/dash/tab-policies.js +12 -0
  34. package/web/dash/tabs.js +581 -0
  35. package/web/dashboard.html +12 -1303
@@ -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
+ })();