@evomap/evolver 1.80.6 → 1.80.8
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/README.zh-CN.md +18 -11
- package/assets/gep/candidates.jsonl +3 -2
- package/index.js +55 -2
- package/package.json +1 -1
- package/src/adapters/opencode.js +137 -2
- package/src/config.js +5 -0
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/gep/.integrity +0 -0
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/assetStore.js +59 -5
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/epigenetics.js +1 -0
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -0
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/integrityCheck.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/shield.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/taskReceiver.js +7 -2
- package/src/webui/client/clientJs/assets.js +111 -0
- package/src/webui/client/clientJs/bootstrap.js +92 -0
- package/src/webui/client/clientJs/common.js +77 -0
- package/src/webui/client/clientJs/i18n.js +366 -0
- package/src/webui/client/clientJs/index.js +35 -0
- package/src/webui/client/clientJs/interactions.js +351 -0
- package/src/webui/client/clientJs/overview.js +152 -0
- package/src/webui/client/clientJs/personality.js +285 -0
- package/src/webui/client/clientJs/pipelines.js +330 -0
- package/src/webui/client/indexHtml.js +221 -0
- package/src/webui/client/static.js +23 -0
- package/src/webui/client/stylesCss.js +639 -0
- package/src/webui/client/vendor/README.md +15 -0
- package/src/webui/client/vendor/echarts.min.js +45 -0
- package/src/webui/index.js +14 -0
- package/src/webui/observer/assets.js +146 -0
- package/src/webui/observer/index.js +37 -0
- package/src/webui/observer/interactions.js +120 -0
- package/src/webui/observer/jsonl.js +75 -0
- package/src/webui/observer/paths.js +46 -0
- package/src/webui/observer/personality.js +43 -0
- package/src/webui/observer/pipelineEvents.js +58 -0
- package/src/webui/observer/redact.js +63 -0
- package/src/webui/observer/runs.js +356 -0
- package/src/webui/observer/safety.js +57 -0
- package/src/webui/observer/skills.js +70 -0
- package/src/webui/observer/status.js +71 -0
- package/src/webui/server/http.js +138 -0
- package/src/webui/server/routes.js +41 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.interactionsJs = `
|
|
4
|
+
const HUB_ACTIVITY_STATE = { layer: 'all', hideHeartbeats: true, events: [] };
|
|
5
|
+
|
|
6
|
+
function buildHubActivityEvents(calls, atpProofs, atpOrders, lifecycleEvents) {
|
|
7
|
+
const events = [];
|
|
8
|
+
(lifecycleEvents || []).forEach((e) => events.push({
|
|
9
|
+
layer: 'lifecycle',
|
|
10
|
+
time: e.ts,
|
|
11
|
+
kind: e.kind,
|
|
12
|
+
outcome: e.outcome,
|
|
13
|
+
statusCode: e.status_code,
|
|
14
|
+
latencyMs: e.latency_ms,
|
|
15
|
+
error: e.error,
|
|
16
|
+
title: e.kind === 'fetch' && e.extra?.skill_id ? 'skill: ' + e.extra.skill_id : (e.node_id || '-'),
|
|
17
|
+
}));
|
|
18
|
+
(calls || []).forEach((c) => events.push({
|
|
19
|
+
layer: 'asset',
|
|
20
|
+
time: c.timestamp,
|
|
21
|
+
kind: c.action,
|
|
22
|
+
outcome: inferAssetOutcome(c.action),
|
|
23
|
+
title: c.asset_id || c.reason || '-',
|
|
24
|
+
meta: c.run_id ? 'run ' + c.run_id : null,
|
|
25
|
+
score: c.score,
|
|
26
|
+
}));
|
|
27
|
+
(atpProofs || []).forEach((p) => events.push({
|
|
28
|
+
layer: 'atp',
|
|
29
|
+
time: p.created_at || p.timestamp,
|
|
30
|
+
kind: 'proof_' + (p.status || 'pending'),
|
|
31
|
+
outcome: p.status === 'verified' || p.status === 'accepted' ? 'ok' : (p.status || 'pending'),
|
|
32
|
+
title: p.delivery_id || p.order_id || '-',
|
|
33
|
+
meta: (p.role || 'consumer') + (p.amount != null ? ' · ' + p.amount + ' credits' : ''),
|
|
34
|
+
}));
|
|
35
|
+
(atpOrders || []).forEach((o) => events.push({
|
|
36
|
+
layer: 'atp',
|
|
37
|
+
time: o.created_at || o.updated_at,
|
|
38
|
+
kind: 'order_' + (o.status || 'pending'),
|
|
39
|
+
outcome: o.status === 'completed' ? 'ok' : (o.status || 'pending'),
|
|
40
|
+
title: o.order_id || o.id || '-',
|
|
41
|
+
meta: (o.routing || '-') + (o.budget != null ? ' · ' + o.budget + ' credits' : ''),
|
|
42
|
+
}));
|
|
43
|
+
return events.sort((a, b) => new Date(b.time || 0) - new Date(a.time || 0));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function inferAssetOutcome(action) {
|
|
47
|
+
if (!action) return 'unknown';
|
|
48
|
+
if (action.endsWith('_hit') || action === 'asset_reuse' || action === 'asset_reference' || action === 'asset_publish') return 'ok';
|
|
49
|
+
if (action.endsWith('_miss') || action.endsWith('_skip')) return 'miss';
|
|
50
|
+
return action;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function summarizeHubActivity(events) {
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const last24h = events.filter((e) => Date.parse(e.time || 0) >= now - 24 * 60 * 60 * 1000);
|
|
56
|
+
const heartbeats = events.filter((e) => e.layer === 'lifecycle' && e.kind === 'heartbeat');
|
|
57
|
+
const heartbeatOk = heartbeats.filter((e) => e.outcome === 'ok' || e.outcome === 'recovered');
|
|
58
|
+
const heartbeatHealthPct = heartbeats.length === 0 ? null : Math.round((heartbeatOk.length / heartbeats.length) * 100);
|
|
59
|
+
const latencies = events.filter((e) => typeof e.latencyMs === 'number').map((e) => e.latencyMs);
|
|
60
|
+
const lastHelloOk = events.find((e) => e.layer === 'lifecycle' && e.kind === 'hello' && e.outcome === 'ok')?.time;
|
|
61
|
+
const lastHeartbeatOk = events.find((e) => e.layer === 'lifecycle' && e.kind === 'heartbeat' && (e.outcome === 'ok' || e.outcome === 'recovered'))?.time;
|
|
62
|
+
const assetEvents = events.filter((e) => e.layer === 'asset');
|
|
63
|
+
const assetHits = assetEvents.filter((e) => e.outcome === 'ok').length;
|
|
64
|
+
const assetHitRate = assetEvents.length === 0 ? null : Math.round((assetHits / assetEvents.length) * 100);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
total: events.length,
|
|
68
|
+
last24h: last24h.length,
|
|
69
|
+
heartbeatHealthPct,
|
|
70
|
+
assetHitRate,
|
|
71
|
+
lastHelloOk,
|
|
72
|
+
lastHeartbeatOk,
|
|
73
|
+
latencyP50: percentile(latencies, 50),
|
|
74
|
+
latencyP95: percentile(latencies, 95),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function percentile(values, p) {
|
|
79
|
+
if (!values.length) return null;
|
|
80
|
+
const sorted = values.slice().sort((a, b) => a - b);
|
|
81
|
+
return sorted[Math.min(sorted.length - 1, Math.floor((sorted.length * p) / 100))];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderHubActivity(events, hasProxy) {
|
|
85
|
+
HUB_ACTIVITY_STATE.events = events;
|
|
86
|
+
HUB_ACTIVITY_STATE.hasProxy = !!hasProxy;
|
|
87
|
+
renderHubActivitySummary(summarizeHubActivity(events));
|
|
88
|
+
bindHubActivityFilters();
|
|
89
|
+
renderHubActivityTable();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderHubActivitySummary(s) {
|
|
93
|
+
const healthCls = s.heartbeatHealthPct == null ? '' : s.heartbeatHealthPct >= 95 ? 'success' : s.heartbeatHealthPct >= 70 ? 'pending' : 'failed';
|
|
94
|
+
const hitCls = s.assetHitRate == null ? '' : s.assetHitRate >= 50 ? 'success' : s.assetHitRate >= 20 ? 'pending' : 'failed';
|
|
95
|
+
$('hub-activity-summary').innerHTML =
|
|
96
|
+
statBox(t('interactions.stat.heartbeatHealth'), s.heartbeatHealthPct == null ? '—' : s.heartbeatHealthPct + '%', healthCls) +
|
|
97
|
+
statBox(t('interactions.stat.assetHitRate'), s.assetHitRate == null ? '—' : s.assetHitRate + '%', hitCls) +
|
|
98
|
+
statBox(t('interactions.stat.events24h'), String(s.last24h ?? 0)) +
|
|
99
|
+
statBox(t('interactions.stat.latency'), s.latencyP50 == null ? '—' : (s.latencyP50 + ' / ' + (s.latencyP95 ?? '—') + ' ms')) +
|
|
100
|
+
statBox(t('interactions.stat.lastHelloOk'), formatTime(s.lastHelloOk)) +
|
|
101
|
+
statBox(t('interactions.stat.lastHeartbeatOk'), formatTime(s.lastHeartbeatOk));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function bindHubActivityFilters() {
|
|
105
|
+
const bar = $('hub-activity-filters');
|
|
106
|
+
if (!bar) return;
|
|
107
|
+
bar.style.display = HUB_ACTIVITY_STATE.events.length ? 'flex' : 'none';
|
|
108
|
+
bar.querySelectorAll('[data-filter-layer]').forEach((btn) => {
|
|
109
|
+
if (btn._bound) return;
|
|
110
|
+
btn._bound = true;
|
|
111
|
+
btn.addEventListener('click', () => {
|
|
112
|
+
HUB_ACTIVITY_STATE.layer = btn.getAttribute('data-filter-layer');
|
|
113
|
+
bar.querySelectorAll('[data-filter-layer]').forEach((b) => b.classList.toggle('active', b === btn));
|
|
114
|
+
renderHubActivityTable();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
const cb = $('hide-heartbeats');
|
|
118
|
+
if (cb && !cb._bound) {
|
|
119
|
+
cb._bound = true;
|
|
120
|
+
cb.checked = HUB_ACTIVITY_STATE.hideHeartbeats;
|
|
121
|
+
cb.addEventListener('change', () => {
|
|
122
|
+
HUB_ACTIVITY_STATE.hideHeartbeats = cb.checked;
|
|
123
|
+
renderHubActivityTable();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function renderHubActivityTable() {
|
|
129
|
+
const filtered = HUB_ACTIVITY_STATE.events.filter((e) => {
|
|
130
|
+
if (HUB_ACTIVITY_STATE.layer !== 'all' && e.layer !== HUB_ACTIVITY_STATE.layer) return false;
|
|
131
|
+
if (HUB_ACTIVITY_STATE.hideHeartbeats && e.layer === 'lifecycle' && e.kind === 'heartbeat' && (e.outcome === 'ok' || e.outcome === 'recovered')) return false;
|
|
132
|
+
return true;
|
|
133
|
+
}).slice(0, 150);
|
|
134
|
+
|
|
135
|
+
if (!filtered.length) {
|
|
136
|
+
$('hub-activity').innerHTML = HUB_ACTIVITY_STATE.events.length
|
|
137
|
+
? '<p class="muted">' + esc(t('interactions.empty.filtered')) + '</p>'
|
|
138
|
+
: hubEmptyHint(HUB_ACTIVITY_STATE.hasProxy);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const rows = filtered.map((e) => {
|
|
143
|
+
const ok = e.outcome === 'ok' || e.outcome === 'recovered';
|
|
144
|
+
const fail = e.outcome && (String(e.outcome).startsWith('fail') || String(e.outcome).startsWith('auth_failed') || String(e.outcome).startsWith('http_'));
|
|
145
|
+
const cls = ok ? 'ok' : fail ? 'fail' : 'neutral';
|
|
146
|
+
return '<tr class="' + cls + '">' +
|
|
147
|
+
'<td>' + esc(formatTime(e.time)) + '</td>' +
|
|
148
|
+
'<td><span class="pill ' + esc(e.layer) + '">' + esc(tAction(e.layer)) + '</span></td>' +
|
|
149
|
+
'<td><span class="pill ' + esc(e.kind || '-') + '">' + esc(tAction(e.kind) || '-') + '</span></td>' +
|
|
150
|
+
'<td><span class="status-indicator ' + (ok ? 'success' : fail ? 'failed' : 'unknown') + '"></span>' + esc(tStatus(e.outcome) || '-') + '</td>' +
|
|
151
|
+
// statusCode / latencyMs are SHOULD-be-numeric but the producer
|
|
152
|
+
// is the proxy daemon, which has historically leaked string
|
|
153
|
+
// shapes ("timeout", "n/a"). Bugbot caught the asymmetry: every
|
|
154
|
+
// other column on this row is esc()'d. Defensive escape here
|
|
155
|
+
// matches the rest of the renderer instead of trusting the
|
|
156
|
+
// proxy to never inject HTML chars.
|
|
157
|
+
'<td>' + esc(e.statusCode ?? '—') + '</td>' +
|
|
158
|
+
'<td>' + esc(e.latencyMs == null ? '—' : e.latencyMs + ' ms') + '</td>' +
|
|
159
|
+
'<td class="lifecycle-error">' + esc(e.title || '') + (e.meta ? ' <span class="muted small">' + esc(e.meta) + '</span>' : '') + (e.error ? ' <span class="status-failed">' + esc(e.error) + '</span>' : '') + '</td>' +
|
|
160
|
+
'</tr>';
|
|
161
|
+
}).join('');
|
|
162
|
+
|
|
163
|
+
$('hub-activity').innerHTML = '<table class="data-table lifecycle-table">' +
|
|
164
|
+
'<thead><tr>' +
|
|
165
|
+
'<th>' + esc(t('interactions.col.time')) + '</th>' +
|
|
166
|
+
'<th>' + esc(t('interactions.col.layer')) + '</th>' +
|
|
167
|
+
'<th>' + esc(t('interactions.col.kind')) + '</th>' +
|
|
168
|
+
'<th>' + esc(t('interactions.col.outcome')) + '</th>' +
|
|
169
|
+
'<th>' + esc(t('interactions.col.status')) + '</th>' +
|
|
170
|
+
'<th>' + esc(t('interactions.col.latency')) + '</th>' +
|
|
171
|
+
'<th>' + esc(t('interactions.col.detail')) + '</th>' +
|
|
172
|
+
'</tr></thead>' +
|
|
173
|
+
'<tbody>' + rows + '</tbody></table>';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function hubEmptyHint(hasProxy) {
|
|
177
|
+
if (hasProxy) {
|
|
178
|
+
return '<p class="muted">' + esc(t('interactions.empty.proxyRunning')) + '</p>';
|
|
179
|
+
}
|
|
180
|
+
return '<p class="muted">' + t('interactions.empty.noProxy') + '</p>';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderAgentStream(mailbox, sessions, dms) {
|
|
184
|
+
const items = [];
|
|
185
|
+
(mailbox || []).forEach((m) => items.push({
|
|
186
|
+
kind: 'mailbox',
|
|
187
|
+
time: m.timestamp,
|
|
188
|
+
action: 'mb_' + (m.direction || 'msg'),
|
|
189
|
+
title: m.summary || m.type || '-',
|
|
190
|
+
meta: (m.type || '-') + ' · ' + (m.status || '-'),
|
|
191
|
+
detail: m,
|
|
192
|
+
}));
|
|
193
|
+
(sessions || []).forEach((s) => items.push({
|
|
194
|
+
kind: 'session',
|
|
195
|
+
time: s.created_at || s.updated_at,
|
|
196
|
+
action: 'session_' + (s.status || 'active'),
|
|
197
|
+
title: s.session_id || s.id || '-',
|
|
198
|
+
meta: 'with ' + (s.peer || s.peer_node_id || '-'),
|
|
199
|
+
detail: s,
|
|
200
|
+
}));
|
|
201
|
+
(dms || []).forEach((d) => items.push({
|
|
202
|
+
kind: 'dm',
|
|
203
|
+
time: d.created_at,
|
|
204
|
+
action: 'dm_' + (d.direction || 'msg'),
|
|
205
|
+
title: d.title || d.message_id || '-',
|
|
206
|
+
meta: (d.from || '-') + ' → ' + (d.to || '-'),
|
|
207
|
+
detail: d,
|
|
208
|
+
}));
|
|
209
|
+
|
|
210
|
+
if (!items.length) {
|
|
211
|
+
$('agent-stream').innerHTML = '<p class="muted">' + esc(t('interactions.empty.noAgent')) + '</p>';
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
items.sort((a, b) => new Date(b.time || 0) - new Date(a.time || 0));
|
|
215
|
+
$('agent-stream').innerHTML = '<ul class="stream-list">' + items.slice(0, 60).map(streamItem).join('') + '</ul>';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function streamItem(item) {
|
|
219
|
+
return '<li class="stream-item">' +
|
|
220
|
+
'<div class="stream-head">' +
|
|
221
|
+
'<span class="pill ' + esc(item.action) + '">' + esc(item.action) + '</span>' +
|
|
222
|
+
'<span class="muted small">' + esc(formatTime(item.time)) + '</span>' +
|
|
223
|
+
'</div>' +
|
|
224
|
+
'<div class="stream-title">' + esc(item.title) + '</div>' +
|
|
225
|
+
'<div class="muted small">' + esc(item.meta) + '</div>' +
|
|
226
|
+
'</li>';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function renderInteractionCharts(calls, atpProofs, mailbox) {
|
|
230
|
+
const textColor = chartTextColor();
|
|
231
|
+
const isDark = isDarkMode();
|
|
232
|
+
|
|
233
|
+
const actionCounts = {};
|
|
234
|
+
(calls || []).forEach((c) => { actionCounts[c.action] = (actionCounts[c.action] || 0) + 1; });
|
|
235
|
+
ensureChart('hubActionChart')?.setOption({
|
|
236
|
+
tooltip: { trigger: 'item' },
|
|
237
|
+
series: [{
|
|
238
|
+
type: 'pie', radius: ['40%', '70%'],
|
|
239
|
+
itemStyle: { borderRadius: 4, borderColor: isDark ? '#181b1f' : '#fff', borderWidth: 2 },
|
|
240
|
+
label: { show: false },
|
|
241
|
+
labelLine: { show: false },
|
|
242
|
+
data: Object.keys(actionCounts).length
|
|
243
|
+
? Object.entries(actionCounts).map(([name, value]) => ({ name, value }))
|
|
244
|
+
: [{ name: 'no calls', value: 1, itemStyle: { color: '#444' } }],
|
|
245
|
+
}],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const dayBuckets = bucketByDay([...(calls || []), ...(atpProofs || []), ...(mailbox || [])], 30);
|
|
249
|
+
ensureChart('activityChart')?.setOption({
|
|
250
|
+
tooltip: { trigger: 'axis' },
|
|
251
|
+
grid: { left: '3%', right: '4%', bottom: '8%', containLabel: true },
|
|
252
|
+
xAxis: { type: 'category', data: dayBuckets.labels, axisLabel: { color: textColor, fontSize: 10 } },
|
|
253
|
+
yAxis: { type: 'value', axisLabel: { color: textColor }, splitLine: { lineStyle: { color: isDark ? '#2c3235' : '#e4e7eb' } } },
|
|
254
|
+
series: [{
|
|
255
|
+
type: 'line', data: dayBuckets.values, smooth: true, areaStyle: { opacity: 0.18, color: '#3274d9' },
|
|
256
|
+
lineStyle: { color: '#3274d9', width: 2 }, itemStyle: { color: '#3274d9' },
|
|
257
|
+
}],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const typeCounts = {};
|
|
261
|
+
(mailbox || []).forEach((m) => { typeCounts[m.type || 'unknown'] = (typeCounts[m.type || 'unknown'] || 0) + 1; });
|
|
262
|
+
ensureChart('mailboxChart')?.setOption({
|
|
263
|
+
tooltip: { trigger: 'axis' },
|
|
264
|
+
grid: { left: '3%', right: '4%', bottom: '5%', containLabel: true },
|
|
265
|
+
xAxis: { type: 'value', axisLabel: { color: textColor }, splitLine: { lineStyle: { color: isDark ? '#2c3235' : '#e4e7eb' } } },
|
|
266
|
+
yAxis: { type: 'category', data: Object.keys(typeCounts).length ? Object.keys(typeCounts) : ['no messages'], axisLabel: { color: textColor } },
|
|
267
|
+
series: [{
|
|
268
|
+
type: 'bar',
|
|
269
|
+
data: Object.keys(typeCounts).length ? Object.values(typeCounts) : [0],
|
|
270
|
+
itemStyle: { color: '#28a745', borderRadius: [0, 4, 4, 0] },
|
|
271
|
+
}],
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function bucketByDay(items, days) {
|
|
276
|
+
const today = new Date(); today.setHours(0,0,0,0);
|
|
277
|
+
const labels = [], counts = new Array(days).fill(0);
|
|
278
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
279
|
+
const d = new Date(today); d.setDate(d.getDate() - i);
|
|
280
|
+
labels.push((d.getMonth()+1) + '/' + d.getDate());
|
|
281
|
+
}
|
|
282
|
+
items.forEach((it) => {
|
|
283
|
+
const t = new Date(it.timestamp || it.time || it.created_at || 0);
|
|
284
|
+
if (isNaN(t.getTime())) return;
|
|
285
|
+
t.setHours(0,0,0,0);
|
|
286
|
+
const diff = Math.round((today - t) / 86400000);
|
|
287
|
+
if (diff >= 0 && diff < days) counts[days - 1 - diff]++;
|
|
288
|
+
});
|
|
289
|
+
return { labels, values: counts };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderProxySnapshots(snapshots) {
|
|
293
|
+
if (!snapshots || !Object.keys(snapshots).length) {
|
|
294
|
+
$('proxy-snapshots').innerHTML = '<p class="muted snapshot-empty">' + t('interactions.empty.proxyNotRunning') + '</p>';
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
$('proxy-snapshots').innerHTML = Object.entries(snapshots).map(([key, snap]) => {
|
|
298
|
+
const ok = snap?.ok;
|
|
299
|
+
const dot = '<span class="status-indicator ' + (ok ? 'status-success' : 'status-failed') + '"></span>';
|
|
300
|
+
const detail = ok && snap.body
|
|
301
|
+
? (Array.isArray(snap.body) ? snap.body.length + ' ' + t('interactions.snapshot.items') : Object.keys(snap.body).length + ' ' + t('interactions.snapshot.fields'))
|
|
302
|
+
: (snap?.error || t('interactions.snapshot.unavailable'));
|
|
303
|
+
return '<div class="snapshot-card"><div>' + dot + '<strong>' + esc(key) + '</strong></div>' +
|
|
304
|
+
'<div class="muted small">' + esc(detail) + '</div></div>';
|
|
305
|
+
}).join('');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function loadInteractions() {
|
|
309
|
+
$('hub-activity').innerHTML = '<p class="muted">' + esc(t('common.loading')) + '</p>';
|
|
310
|
+
$('agent-stream').innerHTML = '<p class="muted">' + esc(t('common.loading')) + '</p>';
|
|
311
|
+
try {
|
|
312
|
+
// Lifecycle is optional: not every build ships /webui/lifecycle (the
|
|
313
|
+
// observer-side module is only present when the proxy daemon is wired
|
|
314
|
+
// in). Treat a missing/erroring lifecycle endpoint as "no data" so the
|
|
315
|
+
// rest of the Hub Activity panel still renders instead of failing the
|
|
316
|
+
// whole tab with "Failed: Not found".
|
|
317
|
+
const [callsResult, interactions, lifecycle] = await Promise.all([
|
|
318
|
+
api('/webui/assets/calls?limit=500'),
|
|
319
|
+
api('/webui/interactions?last=200'),
|
|
320
|
+
api('/webui/lifecycle?last=500').catch(() => ({ events: [] })),
|
|
321
|
+
]);
|
|
322
|
+
const calls = callsResult.data || [];
|
|
323
|
+
const proofs = interactions.proxySnapshots?.atpProofs?.body?.proofs || interactions.proxySnapshots?.atpProofs?.body || [];
|
|
324
|
+
const orders = interactions.proxySnapshots?.atpProofs?.body?.orders || [];
|
|
325
|
+
const sessions = interactions.proxySnapshots?.sessions?.body?.sessions || interactions.proxySnapshots?.sessions?.body || [];
|
|
326
|
+
const dms = interactions.proxySnapshots?.dms?.body?.dms || interactions.proxySnapshots?.dms?.body || [];
|
|
327
|
+
const mailbox = interactions.mailbox?.data || [];
|
|
328
|
+
const lifecycleEvents = lifecycle?.events || [];
|
|
329
|
+
|
|
330
|
+
const unified = buildHubActivityEvents(
|
|
331
|
+
calls,
|
|
332
|
+
Array.isArray(proofs) ? proofs : [],
|
|
333
|
+
Array.isArray(orders) ? orders : [],
|
|
334
|
+
lifecycleEvents,
|
|
335
|
+
);
|
|
336
|
+
renderHubActivity(unified, interactions.proxy?.running);
|
|
337
|
+
renderAgentStream(mailbox, Array.isArray(sessions) ? sessions : [], Array.isArray(dms) ? dms : []);
|
|
338
|
+
renderInteractionCharts(calls, Array.isArray(proofs) ? proofs : [], mailbox);
|
|
339
|
+
renderProxySnapshots(interactions.proxySnapshots);
|
|
340
|
+
} catch (err) {
|
|
341
|
+
$('hub-activity').innerHTML = '<p class="status-failed">' + esc(t('common.failedPrefix')) + esc(err.message) + '</p>';
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function statBox(label, value, cls) {
|
|
346
|
+
return '<div class="stat-box ' + (cls || '') + '">' +
|
|
347
|
+
'<div class="stat-label">' + esc(label) + '</div>' +
|
|
348
|
+
'<div class="stat-value">' + esc(value) + '</div>' +
|
|
349
|
+
'</div>';
|
|
350
|
+
}
|
|
351
|
+
`;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
exports.overviewJs = `
|
|
4
|
+
function renderStatus(status) {
|
|
5
|
+
const lastRun = status.lastRun || {};
|
|
6
|
+
$('status').innerHTML = kv([
|
|
7
|
+
[t('overview.status.mode'), status.mode],
|
|
8
|
+
[t('overview.status.proxy'), status.proxy?.running ? t('common.running') : t('common.notRunning')],
|
|
9
|
+
[t('overview.status.heartbeat'), status.heartbeat?.phase || t('common.idle')],
|
|
10
|
+
[t('overview.status.lastRun'), lastRun.run_id || '-'],
|
|
11
|
+
[t('overview.status.lastActivity'), formatTime(lastRun.finished_at || lastRun.created_at)],
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function renderSafety(safety) {
|
|
16
|
+
const warnings = safety.warnings?.length
|
|
17
|
+
? '<ul style="margin-top:8px;padding-left:20px;color:var(--warning)">' + safety.warnings.map((w) => '<li>' + esc(w) + '</li>').join('') + '</ul>'
|
|
18
|
+
: '<p style="margin-top:8px;color:var(--success)">' + esc(t('overview.safety.noWarnings')) + '</p>';
|
|
19
|
+
$('safety').innerHTML = '<div style="margin-bottom:8px"><span class="status-indicator ' + (safety.safeMode ? 'status-success' : 'status-warning') + '"></span><strong>' + esc(safety.safeMode ? t('overview.safety.safeMode') : t('overview.safety.reviewRequired')) + '</strong></div>' + kv([
|
|
20
|
+
[t('overview.safety.autobuy'), safety.autobuyEnabled],
|
|
21
|
+
[t('overview.safety.autoPublish'), safety.autoPublishEnabled],
|
|
22
|
+
[t('overview.safety.validator'), safety.validatorEnabled],
|
|
23
|
+
[t('overview.safety.traceLevel'), safety.traceLevel],
|
|
24
|
+
]) + warnings;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function renderInteractions(interactions) {
|
|
28
|
+
$('interactions').innerHTML = kv([
|
|
29
|
+
[t('overview.interactions.proxy'), interactions.proxy?.running ? interactions.proxy.url : t('common.notRunning')],
|
|
30
|
+
[t('overview.interactions.mailbox'), interactions.mailbox?.pagination?.totalItems || 0],
|
|
31
|
+
[t('overview.interactions.taskMetrics'), interactions.proxySnapshots?.taskMetrics?.ok ? t('common.available') : t('common.notAvailable')],
|
|
32
|
+
[t('overview.interactions.sessions'), interactions.proxySnapshots?.sessions?.ok ? t('common.available') : t('common.notAvailable')],
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderOverviewCharts(assets) {
|
|
37
|
+
const isDark = isDarkMode();
|
|
38
|
+
const textColor = chartTextColor();
|
|
39
|
+
const palette = ['#3274d9', '#28a745', '#ffc107', '#dc3545', '#6f42c1', '#17a2b8'];
|
|
40
|
+
|
|
41
|
+
ensureChart('genesChart')?.setOption({
|
|
42
|
+
color: palette,
|
|
43
|
+
tooltip: { trigger: 'item' },
|
|
44
|
+
legend: { bottom: 0, textStyle: { color: textColor } },
|
|
45
|
+
series: [{
|
|
46
|
+
type: 'pie',
|
|
47
|
+
radius: ['45%', '72%'],
|
|
48
|
+
itemStyle: { borderRadius: 4, borderColor: isDark ? '#181b1f' : '#fff', borderWidth: 2 },
|
|
49
|
+
label: { show: false },
|
|
50
|
+
labelLine: { show: false },
|
|
51
|
+
data: Object.entries(assets.genesByCategory || {}).map(([name, value]) => ({ name, value })),
|
|
52
|
+
}],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const capsules = Object.entries(assets.capsulesByOutcome || {});
|
|
56
|
+
ensureChart('capsulesChart')?.setOption({
|
|
57
|
+
color: palette,
|
|
58
|
+
tooltip: { trigger: 'item' },
|
|
59
|
+
legend: { bottom: 0, textStyle: { color: textColor } },
|
|
60
|
+
series: [{
|
|
61
|
+
type: 'pie',
|
|
62
|
+
radius: ['45%', '72%'],
|
|
63
|
+
itemStyle: { borderRadius: 4, borderColor: isDark ? '#181b1f' : '#fff', borderWidth: 2 },
|
|
64
|
+
label: { show: false },
|
|
65
|
+
labelLine: { show: false },
|
|
66
|
+
data: capsules.length ? capsules.map(([name, value]) => ({ name, value })) : [{ name: 'no capsules yet', value: 1, itemStyle: { color: '#444' } }],
|
|
67
|
+
}],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const calls = Object.entries(assets.assetCallsByAction || {});
|
|
71
|
+
ensureChart('callsChart')?.setOption({
|
|
72
|
+
color: palette,
|
|
73
|
+
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
|
74
|
+
grid: { left: '3%', right: '4%', bottom: '5%', containLabel: true },
|
|
75
|
+
xAxis: { type: 'value', splitLine: { lineStyle: { color: isDark ? '#2c3235' : '#e4e7eb' } }, axisLabel: { color: textColor } },
|
|
76
|
+
yAxis: { type: 'category', data: calls.length ? calls.map(d => d[0]) : ['no calls'], axisLabel: { color: textColor } },
|
|
77
|
+
series: [{
|
|
78
|
+
type: 'bar',
|
|
79
|
+
data: calls.length ? calls.map(d => d[1]) : [0],
|
|
80
|
+
itemStyle: { color: '#3274d9', borderRadius: [0, 4, 4, 0] },
|
|
81
|
+
}],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderLatestRun(runs) {
|
|
86
|
+
const list = runs.data || [];
|
|
87
|
+
if (!list.length) {
|
|
88
|
+
$('latest-run').innerHTML = '<p class="muted">' + esc(t('overview.run.empty')) + '</p>';
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const run = list[0];
|
|
92
|
+
// Build the <dl> manually: only the Status value contains real HTML
|
|
93
|
+
// (the status-indicator span); other values are plain text and go
|
|
94
|
+
// through esc(). Avoids the kv()+partial-replace dance that left
|
|
95
|
+
// " / > un-restored and broke the indicator render. Cursor
|
|
96
|
+
// Bugbot Medium-severity finding on PR #532 -- see test guard for
|
|
97
|
+
// 'kv-then-partial-replace antipattern' in test/webuiServer.test.js.
|
|
98
|
+
const rows = [
|
|
99
|
+
[t('overview.run.id'), esc(run.runId)],
|
|
100
|
+
[t('overview.run.status'), '<span class="status-indicator ' + getStatusClass(run.status) + '"></span>' + esc(tStatus(run.status))],
|
|
101
|
+
[t('overview.run.selectedGene'), esc(run.selectedGeneId || '-')],
|
|
102
|
+
[t('overview.run.validation'), esc(validationDisplay(run))],
|
|
103
|
+
[t('overview.run.updated'), esc(formatTime(run.updatedAt))],
|
|
104
|
+
[t('overview.run.requiresConfirmation'), run.requiresConfirmation ? t('common.yes') : t('common.no')],
|
|
105
|
+
];
|
|
106
|
+
$('latest-run').innerHTML = '<dl>' +
|
|
107
|
+
rows.map(([k, v]) => '<dt>' + esc(k) + '</dt><dd>' + v + '</dd>').join('') +
|
|
108
|
+
'</dl>';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validationDisplay(run) {
|
|
112
|
+
if (run.validationResult === 'pass') return t('overview.validation.pass');
|
|
113
|
+
if (run.validationResult === 'fail') return t('overview.validation.fail');
|
|
114
|
+
if (run.status === 'review_pending') return t('overview.validation.pendingReview');
|
|
115
|
+
if (run.status === 'running' || run.status === 'pending') return t('overview.validation.inProgress');
|
|
116
|
+
if (run.status === 'failed') return t('overview.validation.notRun');
|
|
117
|
+
return t('overview.validation.notRun');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderSkills(skills) {
|
|
121
|
+
if (!skills.exists || !skills.items.length) {
|
|
122
|
+
$('skills').innerHTML = '<p class="muted">' + esc(t('overview.skills.empty')) + '</p>' +
|
|
123
|
+
'<p class="muted small">' + t('overview.skills.hint') + '</p>';
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
$('skills').innerHTML = '<ul class="skill-list">' + skills.items.map((skill) =>
|
|
127
|
+
'<li><strong>' + esc(skill.name) + '</strong>' +
|
|
128
|
+
(skill.description ? '<p>' + esc(skill.description) + '</p>' : '') +
|
|
129
|
+
'<small class="muted">' + skill.fileCount + ' ' + esc(t('overview.skills.files')) + ' · ' + esc(skill.docFile || t('overview.skills.noDoc')) + '</small></li>'
|
|
130
|
+
).join('') + '</ul>';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function loadOverview() {
|
|
134
|
+
try {
|
|
135
|
+
const [status, runs, assets, interactions, skills] = await Promise.all([
|
|
136
|
+
api('/webui/status'),
|
|
137
|
+
api('/webui/runs?limit=20'),
|
|
138
|
+
api('/webui/assets'),
|
|
139
|
+
api('/webui/interactions?limit=20'),
|
|
140
|
+
api('/webui/skills'),
|
|
141
|
+
]);
|
|
142
|
+
renderStatus(status);
|
|
143
|
+
renderSafety(status.safety || {});
|
|
144
|
+
renderInteractions(interactions);
|
|
145
|
+
renderOverviewCharts(assets);
|
|
146
|
+
renderLatestRun(runs);
|
|
147
|
+
renderSkills(skills);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
console.error(err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
`;
|