@songsid/agend 0.0.3 → 0.0.5
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/agent-cli.js +0 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/quickstart.js +1 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/topic-commands.js +2 -2
- package/dist/topic-commands.js.map +1 -1
- package/package.json +3 -5
- package/dist/fleet-dashboard-html.d.ts +0 -6
- package/dist/fleet-dashboard-html.js +0 -443
- package/dist/fleet-dashboard-html.js.map +0 -1
- package/dist/fleet-health-server.d.ts +0 -35
- package/dist/fleet-health-server.js +0 -290
- package/dist/fleet-health-server.js.map +0 -1
- package/dist/fleet-instructions.d.ts +0 -5
- package/dist/fleet-instructions.js +0 -161
- package/dist/fleet-instructions.js.map +0 -1
- package/dist/fleet-rpc-handlers.d.ts +0 -42
- package/dist/fleet-rpc-handlers.js +0 -356
- package/dist/fleet-rpc-handlers.js.map +0 -1
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Activity / fleet dashboard HTML served by the daemon's health server.
|
|
3
|
-
* Pure constant — extracted from fleet-manager.ts to keep that module under
|
|
4
|
-
* a manageable size (P4.1).
|
|
5
|
-
*/
|
|
6
|
-
export const ACTIVITY_VIEWER_HTML = `<!DOCTYPE html>
|
|
7
|
-
<html lang="en">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="utf-8">
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
11
|
-
<title>AgEnD Activity Viewer</title>
|
|
12
|
-
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
13
|
-
<style>
|
|
14
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
15
|
-
body { background: #0d1117; color: #c9d1d9; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace; }
|
|
16
|
-
.header { padding: 16px 24px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
|
|
17
|
-
.header h1 { font-size: 18px; color: #58a6ff; font-weight: 600; }
|
|
18
|
-
.controls { display: flex; gap: 8px; align-items: center; }
|
|
19
|
-
.controls select, .controls button { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 4px 10px; font-size: 13px; cursor: pointer; }
|
|
20
|
-
.controls button.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }
|
|
21
|
-
.controls button:hover { border-color: #58a6ff; }
|
|
22
|
-
.speed-group { display: flex; gap: 2px; }
|
|
23
|
-
.speed-group button { border-radius: 0; }
|
|
24
|
-
.speed-group button:first-child { border-radius: 6px 0 0 6px; }
|
|
25
|
-
.speed-group button:last-child { border-radius: 0 6px 6px 0; }
|
|
26
|
-
.status { font-size: 12px; color: #8b949e; margin-left: auto; }
|
|
27
|
-
#diagram { padding: 24px; overflow-x: auto; }
|
|
28
|
-
#diagram .mermaid { background: transparent; }
|
|
29
|
-
#diagram svg { max-width: 100%; }
|
|
30
|
-
.feed { padding: 12px 24px; max-height: 300px; overflow-y: auto; border-top: 1px solid #21262d; font-size: 13px; line-height: 1.8; }
|
|
31
|
-
.feed-line { opacity: 0.6; }
|
|
32
|
-
.feed-line.visible { opacity: 1; }
|
|
33
|
-
.feed-line .time { color: #8b949e; }
|
|
34
|
-
.feed-line .msg { color: #58a6ff; }
|
|
35
|
-
.feed-line .tool { color: #d29922; }
|
|
36
|
-
.feed-line .task { color: #3fb950; }
|
|
37
|
-
/* Agent Board */
|
|
38
|
-
.board { padding: 16px 24px; display: flex; gap: 12px; flex-wrap: wrap; border-bottom: 1px solid #21262d; }
|
|
39
|
-
.card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px 14px; min-width: 200px; flex: 1; max-width: 280px; transition: border-color 0.3s; }
|
|
40
|
-
.card.flash { border-color: #58a6ff; }
|
|
41
|
-
.card-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
|
42
|
-
.card-header .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
43
|
-
.card-header .dot.running { background: #3fb950; }
|
|
44
|
-
.card-header .dot.stopped { background: #8b949e; }
|
|
45
|
-
.card-header .dot.crashed { background: #f85149; }
|
|
46
|
-
.card-header .name { font-weight: 600; font-size: 14px; }
|
|
47
|
-
.card-row { font-size: 12px; color: #8b949e; line-height: 1.6; }
|
|
48
|
-
.card-row span { color: #c9d1d9; }
|
|
49
|
-
.card-task { font-size: 12px; color: #d29922; margin-top: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
50
|
-
.board-empty { font-size: 13px; color: #8b949e; padding: 8px 0; }
|
|
51
|
-
.section-label { font-size: 11px; color: #484f58; text-transform: uppercase; letter-spacing: 1px; padding: 10px 24px 0; }
|
|
52
|
-
.tabs { display: flex; gap: 0; padding: 0 24px; border-bottom: 1px solid #21262d; }
|
|
53
|
-
.tab { padding: 8px 16px; font-size: 13px; color: #8b949e; cursor: pointer; border: none; border-bottom: 2px solid transparent; background: none; }
|
|
54
|
-
.tab.active { color: #58a6ff; border-bottom-color: #58a6ff; }
|
|
55
|
-
.tab:hover { color: #c9d1d9; }
|
|
56
|
-
.view { display: none; }
|
|
57
|
-
.view.active { display: block; }
|
|
58
|
-
#graphCanvas { width: 100%; background: #0d1117; display: block; }
|
|
59
|
-
</style>
|
|
60
|
-
</head>
|
|
61
|
-
<body>
|
|
62
|
-
<div class="header">
|
|
63
|
-
<h1>AgEnD Activity</h1>
|
|
64
|
-
<div class="controls">
|
|
65
|
-
<select id="range">
|
|
66
|
-
<option value="1h">1h</option>
|
|
67
|
-
<option value="2h" selected>2h</option>
|
|
68
|
-
<option value="4h">4h</option>
|
|
69
|
-
<option value="8h">8h</option>
|
|
70
|
-
<option value="24h">24h</option>
|
|
71
|
-
</select>
|
|
72
|
-
<button id="btnLoad">Load</button>
|
|
73
|
-
<button id="btnPlay">▶ Play</button>
|
|
74
|
-
<button id="btnPause" style="display:none">⏸ Pause</button>
|
|
75
|
-
<div class="speed-group">
|
|
76
|
-
<button class="speed" data-speed="1">1x</button>
|
|
77
|
-
<button class="speed active" data-speed="2">2x</button>
|
|
78
|
-
<button class="speed" data-speed="5">5x</button>
|
|
79
|
-
<button class="speed" data-speed="10">10x</button>
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
<div class="status" id="status">Ready</div>
|
|
83
|
-
</div>
|
|
84
|
-
<div class="section-label">Agents</div>
|
|
85
|
-
<div class="board" id="board"><div class="board-empty">Loading...</div></div>
|
|
86
|
-
<div class="tabs">
|
|
87
|
-
<button class="tab active" data-view="graph">Network Graph</button>
|
|
88
|
-
<button class="tab" data-view="seq">Sequence Diagram</button>
|
|
89
|
-
</div>
|
|
90
|
-
<div id="viewGraph" class="view active"><canvas id="graphCanvas" height="400"></canvas></div>
|
|
91
|
-
<div id="viewSeq" class="view"><div id="diagram"><div class="mermaid" id="mermaidEl"></div></div></div>
|
|
92
|
-
<div class="feed" id="feed"></div>
|
|
93
|
-
|
|
94
|
-
<script>
|
|
95
|
-
mermaid.initialize({ startOnLoad: false, theme: 'dark', sequence: { mirrorActors: false, messageAlign: 'left' } });
|
|
96
|
-
|
|
97
|
-
let rows = [];
|
|
98
|
-
let speed = 2;
|
|
99
|
-
let playing = false;
|
|
100
|
-
let playTimeout = null;
|
|
101
|
-
let visibleCount = 0;
|
|
102
|
-
|
|
103
|
-
document.querySelectorAll('.speed').forEach(btn => {
|
|
104
|
-
btn.addEventListener('click', () => {
|
|
105
|
-
document.querySelectorAll('.speed').forEach(b => b.classList.remove('active'));
|
|
106
|
-
btn.classList.add('active');
|
|
107
|
-
speed = parseInt(btn.dataset.speed);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
document.getElementById('btnLoad').addEventListener('click', load);
|
|
112
|
-
document.getElementById('btnPlay').addEventListener('click', startReplay);
|
|
113
|
-
document.getElementById('btnPause').addEventListener('click', pauseReplay);
|
|
114
|
-
|
|
115
|
-
async function load() {
|
|
116
|
-
const range = document.getElementById('range').value;
|
|
117
|
-
document.getElementById('status').textContent = 'Loading...';
|
|
118
|
-
try {
|
|
119
|
-
const resp = await fetch('/api/activity?since=' + range + '&limit=500');
|
|
120
|
-
rows = await resp.json();
|
|
121
|
-
document.getElementById('status').textContent = rows.length + ' events loaded';
|
|
122
|
-
visibleCount = rows.length;
|
|
123
|
-
renderFull();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
document.getElementById('status').textContent = 'Error: ' + e.message;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function buildMermaid(entries) {
|
|
130
|
-
const participants = new Set();
|
|
131
|
-
entries.forEach(r => { participants.add(r.sender); if (r.receiver) participants.add(r.receiver); });
|
|
132
|
-
const aliases = new Map();
|
|
133
|
-
let idx = 0;
|
|
134
|
-
participants.forEach(p => {
|
|
135
|
-
const a = p.length > 12 ? String.fromCharCode(65 + idx++) : p;
|
|
136
|
-
aliases.set(p, a);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
let lines = ['sequenceDiagram'];
|
|
140
|
-
aliases.forEach((a, p) => lines.push(' participant ' + a + ' as ' + p));
|
|
141
|
-
|
|
142
|
-
entries.forEach(r => {
|
|
143
|
-
const s = aliases.get(r.sender) || r.sender;
|
|
144
|
-
const summary = (r.summary || '').replace(/"/g, "'").slice(0, 80);
|
|
145
|
-
if (r.event === 'tool_call') {
|
|
146
|
-
lines.push(' Note over ' + s + ': 🔧 ' + summary);
|
|
147
|
-
} else if (r.receiver) {
|
|
148
|
-
const recv = aliases.get(r.receiver) || r.receiver;
|
|
149
|
-
lines.push(' ' + s + '->>' + recv + ': ' + summary);
|
|
150
|
-
} else {
|
|
151
|
-
lines.push(' Note over ' + s + ': ' + summary);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
return lines.join('\\n');
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async function renderDiagram(entries) {
|
|
158
|
-
const code = buildMermaid(entries);
|
|
159
|
-
const el = document.getElementById('mermaidEl');
|
|
160
|
-
el.removeAttribute('data-processed');
|
|
161
|
-
el.innerHTML = code;
|
|
162
|
-
try { await mermaid.run({ nodes: [el] }); } catch {}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function renderFeed(count) {
|
|
166
|
-
const feed = document.getElementById('feed');
|
|
167
|
-
feed.innerHTML = '';
|
|
168
|
-
rows.forEach((r, i) => {
|
|
169
|
-
const vis = i < count;
|
|
170
|
-
const time = (r.timestamp || '').replace('T', ' ').slice(11, 19);
|
|
171
|
-
const icon = r.event === 'message' ? '💬' : r.event === 'tool_call' ? '🔧' : '📋';
|
|
172
|
-
const cls = r.event === 'tool_call' ? 'tool' : r.event === 'task_update' ? 'task' : 'msg';
|
|
173
|
-
const arrow = r.receiver ? r.sender + ' → ' + r.receiver : r.sender;
|
|
174
|
-
const line = document.createElement('div');
|
|
175
|
-
line.className = 'feed-line' + (vis ? ' visible' : '');
|
|
176
|
-
line.innerHTML = '<span class="time">' + time + '</span> ' + icon + ' <span class="' + cls + '">' + arrow + ': ' + (r.summary || '') + '</span>';
|
|
177
|
-
feed.appendChild(line);
|
|
178
|
-
});
|
|
179
|
-
if (count > 0) feed.lastElementChild?.scrollIntoView({ behavior: 'smooth' });
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function renderFull() {
|
|
183
|
-
visibleCount = rows.length;
|
|
184
|
-
renderDiagram(rows);
|
|
185
|
-
renderFeed(rows.length);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function startReplay() {
|
|
189
|
-
playing = true;
|
|
190
|
-
visibleCount = 0;
|
|
191
|
-
document.getElementById('btnPlay').style.display = 'none';
|
|
192
|
-
document.getElementById('btnPause').style.display = '';
|
|
193
|
-
stepReplay();
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function pauseReplay() {
|
|
197
|
-
playing = false;
|
|
198
|
-
if (playTimeout) clearTimeout(playTimeout);
|
|
199
|
-
document.getElementById('btnPlay').style.display = '';
|
|
200
|
-
document.getElementById('btnPause').style.display = 'none';
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function stepReplay() {
|
|
204
|
-
if (!playing || visibleCount >= rows.length) {
|
|
205
|
-
pauseReplay();
|
|
206
|
-
document.getElementById('status').textContent = 'Replay complete';
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
visibleCount++;
|
|
210
|
-
const visible = rows.slice(0, visibleCount);
|
|
211
|
-
renderDiagram(visible);
|
|
212
|
-
renderFeed(visibleCount);
|
|
213
|
-
document.getElementById('status').textContent = visibleCount + '/' + rows.length;
|
|
214
|
-
|
|
215
|
-
// Calculate delay from real timestamps
|
|
216
|
-
let delayMs = 500;
|
|
217
|
-
if (visibleCount < rows.length) {
|
|
218
|
-
const curr = new Date(rows[visibleCount - 1].timestamp).getTime();
|
|
219
|
-
const next = new Date(rows[visibleCount].timestamp).getTime();
|
|
220
|
-
delayMs = Math.max(100, Math.min(3000, (next - curr) / speed));
|
|
221
|
-
}
|
|
222
|
-
playTimeout = setTimeout(stepReplay, delayMs);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ── Tab switching ────────────────────────────────
|
|
226
|
-
document.querySelectorAll('.tab').forEach(tab => {
|
|
227
|
-
tab.addEventListener('click', () => {
|
|
228
|
-
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
229
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
230
|
-
tab.classList.add('active');
|
|
231
|
-
document.getElementById('view' + (tab.dataset.view === 'graph' ? 'Graph' : 'Seq')).classList.add('active');
|
|
232
|
-
if (tab.dataset.view === 'graph') resizeCanvas();
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// ── Network Graph ────────────────────────────────
|
|
237
|
-
const canvas = document.getElementById('graphCanvas');
|
|
238
|
-
const ctx2d = canvas.getContext('2d');
|
|
239
|
-
let graphNodes = []; // {name, x, y, color, isGeneral}
|
|
240
|
-
let graphEdges = new Map(); // "a->b" → {from, to}
|
|
241
|
-
let pulses = []; // {fromX, fromY, toX, toY, progress, color}
|
|
242
|
-
|
|
243
|
-
function resizeCanvas() {
|
|
244
|
-
canvas.width = canvas.parentElement.offsetWidth;
|
|
245
|
-
canvas.height = 400;
|
|
246
|
-
layoutNodes();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function layoutNodes() {
|
|
250
|
-
if (graphNodes.length === 0) return;
|
|
251
|
-
const cx = canvas.width / 2;
|
|
252
|
-
const cy = canvas.height / 2;
|
|
253
|
-
const radius = Math.min(cx, cy) - 60;
|
|
254
|
-
// Find general (center)
|
|
255
|
-
const general = graphNodes.find(n => n.isGeneral);
|
|
256
|
-
const others = graphNodes.filter(n => !n.isGeneral);
|
|
257
|
-
if (general) { general.x = cx; general.y = cy; }
|
|
258
|
-
others.forEach((n, i) => {
|
|
259
|
-
const angle = (2 * Math.PI * i / others.length) - Math.PI / 2;
|
|
260
|
-
n.x = cx + radius * Math.cos(angle);
|
|
261
|
-
n.y = cy + radius * Math.sin(angle);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function updateGraphFromFleet(data) {
|
|
266
|
-
const names = new Set();
|
|
267
|
-
data.instances.forEach(inst => names.add(inst.name));
|
|
268
|
-
// Add user node if activity mentions it
|
|
269
|
-
rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });
|
|
270
|
-
// Rebuild nodes (preserve positions if same set)
|
|
271
|
-
const oldMap = new Map(graphNodes.map(n => [n.name, n]));
|
|
272
|
-
graphNodes = [...names].map(name => {
|
|
273
|
-
const old = oldMap.get(name);
|
|
274
|
-
const inst = data.instances.find(i => i.name === name);
|
|
275
|
-
const color = !inst ? '#8b949e' : inst.status === 'running' ? '#3fb950' : inst.status === 'crashed' ? '#f85149' : '#484f58';
|
|
276
|
-
return { name, x: old?.x ?? 0, y: old?.y ?? 0, color, isGeneral: inst?.general_topic ?? false };
|
|
277
|
-
});
|
|
278
|
-
layoutNodes();
|
|
279
|
-
// Build edges from activity
|
|
280
|
-
graphEdges.clear();
|
|
281
|
-
rows.forEach(r => {
|
|
282
|
-
if (r.receiver && r.event === 'message') {
|
|
283
|
-
const key = r.sender + '->' + r.receiver;
|
|
284
|
-
graphEdges.set(key, { from: r.sender, to: r.receiver });
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function spawnPulse(sender, receiver, event) {
|
|
290
|
-
const from = graphNodes.find(n => n.name === sender);
|
|
291
|
-
const to = graphNodes.find(n => n.name === (receiver || sender));
|
|
292
|
-
if (!from || !to) return;
|
|
293
|
-
const colors = { message: '#58a6ff', tool_call: '#d29922', task_update: '#3fb950' };
|
|
294
|
-
pulses.push({ fromX: from.x, fromY: from.y, toX: to.x, toY: to.y, progress: 0, color: colors[event] || '#58a6ff' });
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function drawGraph() {
|
|
298
|
-
if (!ctx2d) return;
|
|
299
|
-
ctx2d.clearRect(0, 0, canvas.width, canvas.height);
|
|
300
|
-
// Draw edges
|
|
301
|
-
ctx2d.strokeStyle = '#21262d';
|
|
302
|
-
ctx2d.lineWidth = 1;
|
|
303
|
-
graphEdges.forEach(e => {
|
|
304
|
-
const from = graphNodes.find(n => n.name === e.from);
|
|
305
|
-
const to = graphNodes.find(n => n.name === e.to);
|
|
306
|
-
if (from && to) {
|
|
307
|
-
ctx2d.beginPath();
|
|
308
|
-
ctx2d.moveTo(from.x, from.y);
|
|
309
|
-
ctx2d.lineTo(to.x, to.y);
|
|
310
|
-
ctx2d.stroke();
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
// Draw pulses
|
|
314
|
-
pulses = pulses.filter(p => p.progress <= 1);
|
|
315
|
-
pulses.forEach(p => {
|
|
316
|
-
p.progress += 0.02;
|
|
317
|
-
const x = p.fromX + (p.toX - p.fromX) * p.progress;
|
|
318
|
-
const y = p.fromY + (p.toY - p.fromY) * p.progress;
|
|
319
|
-
ctx2d.beginPath();
|
|
320
|
-
ctx2d.arc(x, y, 5, 0, Math.PI * 2);
|
|
321
|
-
ctx2d.fillStyle = p.color;
|
|
322
|
-
ctx2d.shadowColor = p.color;
|
|
323
|
-
ctx2d.shadowBlur = 12;
|
|
324
|
-
ctx2d.fill();
|
|
325
|
-
ctx2d.shadowBlur = 0;
|
|
326
|
-
});
|
|
327
|
-
// Draw nodes
|
|
328
|
-
graphNodes.forEach(n => {
|
|
329
|
-
// Glow
|
|
330
|
-
ctx2d.beginPath();
|
|
331
|
-
ctx2d.arc(n.x, n.y, n.isGeneral ? 28 : 22, 0, Math.PI * 2);
|
|
332
|
-
ctx2d.fillStyle = n.color + '22';
|
|
333
|
-
ctx2d.fill();
|
|
334
|
-
// Circle
|
|
335
|
-
ctx2d.beginPath();
|
|
336
|
-
ctx2d.arc(n.x, n.y, n.isGeneral ? 24 : 18, 0, Math.PI * 2);
|
|
337
|
-
ctx2d.fillStyle = '#161b22';
|
|
338
|
-
ctx2d.strokeStyle = n.color;
|
|
339
|
-
ctx2d.lineWidth = 2;
|
|
340
|
-
ctx2d.fill();
|
|
341
|
-
ctx2d.stroke();
|
|
342
|
-
// Label
|
|
343
|
-
ctx2d.fillStyle = '#c9d1d9';
|
|
344
|
-
ctx2d.font = (n.isGeneral ? '12' : '11') + 'px -apple-system, monospace';
|
|
345
|
-
ctx2d.textAlign = 'center';
|
|
346
|
-
ctx2d.fillText(n.name.length > 14 ? n.name.slice(0, 12) + '..' : n.name, n.x, n.y + (n.isGeneral ? 38 : 32));
|
|
347
|
-
});
|
|
348
|
-
requestAnimationFrame(drawGraph);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Hook into replay: spawn pulses when stepping
|
|
352
|
-
const origStep = stepReplay;
|
|
353
|
-
stepReplay = function() {
|
|
354
|
-
const prevCount = visibleCount;
|
|
355
|
-
origStep();
|
|
356
|
-
if (visibleCount > prevCount && visibleCount <= rows.length) {
|
|
357
|
-
const r = rows[visibleCount - 1];
|
|
358
|
-
spawnPulse(r.sender, r.receiver, r.event);
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
// Hook into full load: spawn pulses for all visible events on load
|
|
363
|
-
const origRenderFull = renderFull;
|
|
364
|
-
renderFull = function() {
|
|
365
|
-
origRenderFull();
|
|
366
|
-
// Update graph nodes from fleet data (if available)
|
|
367
|
-
fetch('/api/fleet').then(r => r.json()).then(data => {
|
|
368
|
-
updateGraphFromFleet(data);
|
|
369
|
-
}).catch(() => {
|
|
370
|
-
// Fallback: build nodes from activity only
|
|
371
|
-
const names = new Set();
|
|
372
|
-
rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });
|
|
373
|
-
graphNodes = [...names].map(n => ({ name: n, x: 0, y: 0, color: '#8b949e', isGeneral: n === 'general' }));
|
|
374
|
-
layoutNodes();
|
|
375
|
-
});
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
resizeCanvas();
|
|
379
|
-
window.addEventListener('resize', resizeCanvas);
|
|
380
|
-
requestAnimationFrame(drawGraph);
|
|
381
|
-
|
|
382
|
-
// ── Agent Board ──────────────────────────────────
|
|
383
|
-
|
|
384
|
-
let prevBoard = '';
|
|
385
|
-
|
|
386
|
-
async function loadBoard() {
|
|
387
|
-
try {
|
|
388
|
-
const resp = await fetch('/api/fleet');
|
|
389
|
-
const data = await resp.json();
|
|
390
|
-
renderBoard(data);
|
|
391
|
-
} catch {}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function renderBoard(data) {
|
|
395
|
-
const board = document.getElementById('board');
|
|
396
|
-
const cards = data.instances.map(inst => {
|
|
397
|
-
const statusDot = inst.status === 'running' ? 'running' : inst.status === 'crashed' ? 'crashed' : 'stopped';
|
|
398
|
-
const icon = inst.status === 'running' ? '🟢' : inst.status === 'crashed' ? '🔴' : '⚪';
|
|
399
|
-
const role = inst.general_topic ? 'coordinator' : inst.description || 'worker';
|
|
400
|
-
const costStr = '$' + (inst.costCents / 100).toFixed(2);
|
|
401
|
-
const lastMs = inst.lastActivity;
|
|
402
|
-
let lastStr = '—';
|
|
403
|
-
if (lastMs) {
|
|
404
|
-
const ago = Math.floor((Date.now() - lastMs) / 1000);
|
|
405
|
-
lastStr = ago < 60 ? ago + 's ago' : ago < 3600 ? Math.floor(ago/60) + 'm ago' : Math.floor(ago/3600) + 'h ago';
|
|
406
|
-
}
|
|
407
|
-
const ipc = inst.ipc ? '✓' : '✗';
|
|
408
|
-
const rl = inst.rateLimits ? ' · 5h:' + inst.rateLimits.five_hour_pct + '%' : '';
|
|
409
|
-
const taskLine = inst.currentTask
|
|
410
|
-
? '<div class="card-task">📌 ' + inst.currentTask + '</div>'
|
|
411
|
-
: '<div class="card-task" style="color:#484f58">(idle)</div>';
|
|
412
|
-
return '<div class="card" data-name="' + inst.name + '">' +
|
|
413
|
-
'<div class="card-header"><div class="dot ' + statusDot + '"></div><div class="name">' + inst.name + '</div></div>' +
|
|
414
|
-
'<div class="card-row">' + role.slice(0, 30) + '</div>' +
|
|
415
|
-
'<div class="card-row">Backend: <span>' + inst.backend + '</span> · Tools: <span>' + inst.tool_set + '</span></div>' +
|
|
416
|
-
'<div class="card-row">IPC: <span>' + ipc + '</span> · Cost: <span>' + costStr + '</span>' + rl + '</div>' +
|
|
417
|
-
'<div class="card-row">Last: <span>' + lastStr + '</span></div>' +
|
|
418
|
-
taskLine +
|
|
419
|
-
'</div>';
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
const newHtml = cards.join('');
|
|
423
|
-
if (newHtml !== prevBoard) {
|
|
424
|
-
board.innerHTML = newHtml;
|
|
425
|
-
// Flash changed cards
|
|
426
|
-
board.querySelectorAll('.card').forEach(c => {
|
|
427
|
-
c.classList.add('flash');
|
|
428
|
-
setTimeout(() => c.classList.remove('flash'), 1000);
|
|
429
|
-
});
|
|
430
|
-
prevBoard = newHtml;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Auto-refresh board every 10s
|
|
435
|
-
setInterval(loadBoard, 10000);
|
|
436
|
-
|
|
437
|
-
// Auto-load on page open
|
|
438
|
-
loadBoard();
|
|
439
|
-
load();
|
|
440
|
-
</script>
|
|
441
|
-
</body>
|
|
442
|
-
</html>`;
|
|
443
|
-
//# sourceMappingURL=fleet-dashboard-html.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-dashboard-html.js","sourceRoot":"","sources":["../src/fleet-dashboard-html.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAob5B,CAAC"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { type Server } from "node:http";
|
|
2
|
-
import type { Logger } from "./logger.js";
|
|
3
|
-
import type { FleetConfig, InstanceConfig } from "./types.js";
|
|
4
|
-
import type { Scheduler } from "./scheduler/index.js";
|
|
5
|
-
import type { EventLog } from "./event-log.js";
|
|
6
|
-
import type { SysInfo } from "./fleet-context.js";
|
|
7
|
-
/**
|
|
8
|
-
* Extract a web token from a request, accepting (in order):
|
|
9
|
-
* 1. `?token=` query string
|
|
10
|
-
* 2. `Authorization: Bearer <token>` header (standard)
|
|
11
|
-
* 3. `X-Agend-Token: <token>` header (legacy compatibility)
|
|
12
|
-
*/
|
|
13
|
-
export declare function extractWebToken(parsedUrl: URL, headers: Record<string, string | string[] | undefined>): string | null;
|
|
14
|
-
export interface UiStatusContext {
|
|
15
|
-
readonly fleetConfig: FleetConfig | null;
|
|
16
|
-
readonly logger: Logger;
|
|
17
|
-
getInstanceDir(name: string): string;
|
|
18
|
-
getInstanceStatus(name: string): "running" | "stopped" | "crashed";
|
|
19
|
-
}
|
|
20
|
-
export interface HealthServerContext extends UiStatusContext {
|
|
21
|
-
readonly dataDir: string;
|
|
22
|
-
readonly scheduler: Scheduler | null;
|
|
23
|
-
readonly eventLog: EventLog | null;
|
|
24
|
-
getSysInfo(): SysInfo;
|
|
25
|
-
lastActivityMs(name: string): number;
|
|
26
|
-
startInstance(name: string, config: InstanceConfig, topicMode: boolean): Promise<void>;
|
|
27
|
-
restartSingleInstance(name: string): Promise<void>;
|
|
28
|
-
emitSseEvent(event: string, data: unknown): void;
|
|
29
|
-
}
|
|
30
|
-
export declare function startHealthServer(ctx: HealthServerContext, port: number): {
|
|
31
|
-
server: Server;
|
|
32
|
-
webToken: string;
|
|
33
|
-
startedAt: number;
|
|
34
|
-
};
|
|
35
|
-
export declare function getUiStatus(ctx: UiStatusContext, startedAt: number): unknown;
|