@covibes/zeroshot 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +167 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/cli/index.js +3990 -0
- package/cluster-templates/base-templates/debug-workflow.json +181 -0
- package/cluster-templates/base-templates/full-workflow.json +455 -0
- package/cluster-templates/base-templates/single-worker.json +48 -0
- package/cluster-templates/base-templates/worker-validator.json +131 -0
- package/cluster-templates/conductor-bootstrap.json +122 -0
- package/cluster-templates/conductor-junior-bootstrap.json +69 -0
- package/docker/zeroshot-cluster/Dockerfile +132 -0
- package/lib/completion.js +174 -0
- package/lib/id-detector.js +53 -0
- package/lib/settings.js +97 -0
- package/lib/stream-json-parser.js +236 -0
- package/package.json +121 -0
- package/src/agent/agent-config.js +121 -0
- package/src/agent/agent-context-builder.js +241 -0
- package/src/agent/agent-hook-executor.js +329 -0
- package/src/agent/agent-lifecycle.js +555 -0
- package/src/agent/agent-stuck-detector.js +256 -0
- package/src/agent/agent-task-executor.js +1034 -0
- package/src/agent/agent-trigger-evaluator.js +67 -0
- package/src/agent-wrapper.js +459 -0
- package/src/agents/git-pusher-agent.json +20 -0
- package/src/attach/attach-client.js +438 -0
- package/src/attach/attach-server.js +543 -0
- package/src/attach/index.js +35 -0
- package/src/attach/protocol.js +220 -0
- package/src/attach/ring-buffer.js +121 -0
- package/src/attach/socket-discovery.js +242 -0
- package/src/claude-task-runner.js +468 -0
- package/src/config-router.js +80 -0
- package/src/config-validator.js +598 -0
- package/src/github.js +103 -0
- package/src/isolation-manager.js +1042 -0
- package/src/ledger.js +429 -0
- package/src/logic-engine.js +223 -0
- package/src/message-bus-bridge.js +139 -0
- package/src/message-bus.js +202 -0
- package/src/name-generator.js +232 -0
- package/src/orchestrator.js +1938 -0
- package/src/schemas/sub-cluster.js +156 -0
- package/src/sub-cluster-wrapper.js +545 -0
- package/src/task-runner.js +28 -0
- package/src/template-resolver.js +347 -0
- package/src/tui/CHANGES.txt +133 -0
- package/src/tui/LAYOUT.md +261 -0
- package/src/tui/README.txt +192 -0
- package/src/tui/TWO-LEVEL-NAVIGATION.md +186 -0
- package/src/tui/data-poller.js +325 -0
- package/src/tui/demo.js +208 -0
- package/src/tui/formatters.js +123 -0
- package/src/tui/index.js +193 -0
- package/src/tui/keybindings.js +383 -0
- package/src/tui/layout.js +317 -0
- package/src/tui/renderer.js +194 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Dashboard Layout
|
|
3
|
+
* Creates a blessed-contrib grid with multiple widgets for cluster monitoring
|
|
4
|
+
*
|
|
5
|
+
* Layout Grid (20 rows x 12 columns):
|
|
6
|
+
* - Rows 0-5: Clusters Table (cols 0-7) | System Stats (cols 8-11)
|
|
7
|
+
* - Rows 6-11: Agents Table (cols 0-11)
|
|
8
|
+
* - Rows 12-17: Live Logs (cols 0-11)
|
|
9
|
+
* - Rows 18-19: Help Bar (cols 0-11)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const blessed = require('blessed');
|
|
13
|
+
const contrib = require('blessed-contrib');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create main TUI layout with grid-based widget organization
|
|
17
|
+
* @param {blessed.screen} screen - Blessed screen instance
|
|
18
|
+
* @returns {object} Layout object containing all widgets
|
|
19
|
+
*/
|
|
20
|
+
function createLayout(screen) {
|
|
21
|
+
// Create 20x12 grid for responsive layout
|
|
22
|
+
const grid = new contrib.grid({ rows: 20, cols: 12, screen });
|
|
23
|
+
|
|
24
|
+
// ============================================================
|
|
25
|
+
// OVERVIEW MODE LAYOUT:
|
|
26
|
+
// - Clusters Table (0-16 rows, 8 cols) - LARGE
|
|
27
|
+
// - System Stats (0-6 rows, 4 cols)
|
|
28
|
+
// - Help Bar (18-20 rows, 12 cols)
|
|
29
|
+
//
|
|
30
|
+
// DETAIL MODE LAYOUT:
|
|
31
|
+
// - Agents Table (0-9 rows, 12 cols)
|
|
32
|
+
// - Logs (9-18 rows, 12 cols)
|
|
33
|
+
// - Help Bar (18-20 rows, 12 cols)
|
|
34
|
+
// ============================================================
|
|
35
|
+
|
|
36
|
+
const clustersTable = grid.set(0, 0, 16, 8, contrib.table, {
|
|
37
|
+
keys: true,
|
|
38
|
+
fg: 'white',
|
|
39
|
+
selectedFg: 'black',
|
|
40
|
+
selectedBg: 'cyan',
|
|
41
|
+
interactive: true,
|
|
42
|
+
label: ' Clusters ',
|
|
43
|
+
border: { type: 'line', fg: 'cyan' },
|
|
44
|
+
columnSpacing: 2,
|
|
45
|
+
columnWidth: [15, 12, 8, 10, 8],
|
|
46
|
+
style: {
|
|
47
|
+
header: {
|
|
48
|
+
fg: 'cyan',
|
|
49
|
+
bold: true,
|
|
50
|
+
},
|
|
51
|
+
cell: {
|
|
52
|
+
selected: {
|
|
53
|
+
fg: 'black',
|
|
54
|
+
bg: 'cyan',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Set initial columns for clusters table
|
|
61
|
+
clustersTable.setData({
|
|
62
|
+
headers: ['ID', 'Status', 'Agents', 'Config', 'Uptime'],
|
|
63
|
+
data: [],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const statsBox = grid.set(0, 8, 16, 4, blessed.box, {
|
|
67
|
+
label: ' System Stats ',
|
|
68
|
+
content: '',
|
|
69
|
+
tags: true,
|
|
70
|
+
border: { type: 'line', fg: 'cyan' },
|
|
71
|
+
style: {
|
|
72
|
+
border: { fg: 'cyan' },
|
|
73
|
+
label: { fg: 'cyan' },
|
|
74
|
+
},
|
|
75
|
+
padding: {
|
|
76
|
+
left: 2,
|
|
77
|
+
right: 2,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ============================================================
|
|
82
|
+
// AGENTS TABLE (Detail mode only - 9 rows x 12 cols full width)
|
|
83
|
+
// ============================================================
|
|
84
|
+
|
|
85
|
+
const agentTable = grid.set(0, 0, 9, 12, contrib.table, {
|
|
86
|
+
keys: true,
|
|
87
|
+
fg: 'white',
|
|
88
|
+
selectedFg: 'black',
|
|
89
|
+
selectedBg: 'cyan',
|
|
90
|
+
interactive: true,
|
|
91
|
+
label: ' Agents ',
|
|
92
|
+
border: { type: 'line', fg: 'cyan' },
|
|
93
|
+
columnSpacing: 1,
|
|
94
|
+
columnWidth: [12, 15, 12, 8, 8, 10, 10],
|
|
95
|
+
style: {
|
|
96
|
+
header: {
|
|
97
|
+
fg: 'cyan',
|
|
98
|
+
bold: true,
|
|
99
|
+
},
|
|
100
|
+
cell: {
|
|
101
|
+
selected: {
|
|
102
|
+
fg: 'black',
|
|
103
|
+
bg: 'cyan',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Set initial columns for agents table
|
|
110
|
+
agentTable.setData({
|
|
111
|
+
headers: ['Cluster ID', 'Agent ID', 'Role', 'Status', 'Iter', 'CPU', 'Memory'],
|
|
112
|
+
data: [],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ============================================================
|
|
116
|
+
// LOGS (Detail mode only - 9 rows x 12 cols full width)
|
|
117
|
+
// ============================================================
|
|
118
|
+
|
|
119
|
+
const logsBox = grid.set(9, 0, 9, 12, contrib.log, {
|
|
120
|
+
fg: 'white',
|
|
121
|
+
label: ' Live Logs ',
|
|
122
|
+
border: { type: 'line', fg: 'cyan' },
|
|
123
|
+
tags: true,
|
|
124
|
+
style: {
|
|
125
|
+
border: { fg: 'cyan' },
|
|
126
|
+
label: { fg: 'cyan' },
|
|
127
|
+
text: { fg: 'white' },
|
|
128
|
+
},
|
|
129
|
+
scrollable: true,
|
|
130
|
+
mouse: true,
|
|
131
|
+
keyable: true,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ============================================================
|
|
135
|
+
// HELP BAR (2 rows x 12 cols):
|
|
136
|
+
// - Keyboard shortcuts and commands
|
|
137
|
+
// ============================================================
|
|
138
|
+
|
|
139
|
+
const helpBar = grid.set(18, 0, 2, 12, blessed.box, {
|
|
140
|
+
label: ' Help ',
|
|
141
|
+
content:
|
|
142
|
+
'{cyan-fg}[Enter]{/} View ' +
|
|
143
|
+
'{cyan-fg}[↑/↓]{/} Navigate ' +
|
|
144
|
+
'{cyan-fg}[K]{/} Kill ' +
|
|
145
|
+
'{cyan-fg}[s]{/} Stop ' +
|
|
146
|
+
'{cyan-fg}[l]{/} Logs ' +
|
|
147
|
+
'{cyan-fg}[r]{/} Refresh ' +
|
|
148
|
+
'{cyan-fg}[q]{/} Quit',
|
|
149
|
+
tags: true,
|
|
150
|
+
border: { type: 'line', fg: 'cyan' },
|
|
151
|
+
style: {
|
|
152
|
+
border: { fg: 'cyan' },
|
|
153
|
+
label: { fg: 'cyan' },
|
|
154
|
+
text: { fg: 'white' },
|
|
155
|
+
},
|
|
156
|
+
padding: {
|
|
157
|
+
left: 1,
|
|
158
|
+
right: 1,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Focus on clusters table by default
|
|
163
|
+
clustersTable.focus();
|
|
164
|
+
|
|
165
|
+
// Initially hide agent table and logs (overview mode)
|
|
166
|
+
agentTable.hide();
|
|
167
|
+
logsBox.hide();
|
|
168
|
+
|
|
169
|
+
// ============================================================
|
|
170
|
+
// Widget Navigation
|
|
171
|
+
// ============================================================
|
|
172
|
+
|
|
173
|
+
const widgets = [clustersTable, agentTable, logsBox];
|
|
174
|
+
let currentFocus = 0;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Cycle focus to next widget (Tab key)
|
|
178
|
+
*/
|
|
179
|
+
screen.key(['tab'], () => {
|
|
180
|
+
currentFocus = (currentFocus + 1) % widgets.length;
|
|
181
|
+
widgets[currentFocus].focus();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Cycle focus to previous widget (Shift+Tab)
|
|
186
|
+
*/
|
|
187
|
+
screen.key(['shift-tab'], () => {
|
|
188
|
+
currentFocus = (currentFocus - 1 + widgets.length) % widgets.length;
|
|
189
|
+
widgets[currentFocus].focus();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ============================================================
|
|
193
|
+
// Return all widgets for external access
|
|
194
|
+
// ============================================================
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
screen,
|
|
198
|
+
grid,
|
|
199
|
+
clustersTable,
|
|
200
|
+
statsBox,
|
|
201
|
+
agentTable,
|
|
202
|
+
logsBox,
|
|
203
|
+
helpBar,
|
|
204
|
+
widgets,
|
|
205
|
+
focus: (widgetIndex) => {
|
|
206
|
+
if (widgetIndex >= 0 && widgetIndex < widgets.length) {
|
|
207
|
+
currentFocus = widgetIndex;
|
|
208
|
+
widgets[widgetIndex].focus();
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
getCurrentFocus: () => currentFocus,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Update clusters table with current cluster data
|
|
217
|
+
* @param {object} clustersTable - Clusters table widget
|
|
218
|
+
* @param {array} clusters - Array of cluster objects with properties: id, status, agentCount, config, uptime
|
|
219
|
+
*/
|
|
220
|
+
function updateClustersTable(clustersTable, clusters) {
|
|
221
|
+
const data = clusters.map((cluster) => [
|
|
222
|
+
cluster.id || 'N/A',
|
|
223
|
+
cluster.status || 'unknown',
|
|
224
|
+
String(cluster.agentCount || 0),
|
|
225
|
+
cluster.config || 'N/A',
|
|
226
|
+
cluster.uptime || '0s',
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
clustersTable.setData({
|
|
230
|
+
headers: ['ID', 'Status', 'Agents', 'Config', 'Uptime'],
|
|
231
|
+
data,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Update agents table with current agent data
|
|
237
|
+
* @param {object} agentTable - Agents table widget
|
|
238
|
+
* @param {array} agents - Array of agent objects
|
|
239
|
+
*/
|
|
240
|
+
function updateAgentsTable(agentTable, agents) {
|
|
241
|
+
const data = agents.map((agent) => [
|
|
242
|
+
agent.clusterId || 'N/A',
|
|
243
|
+
agent.id || 'N/A',
|
|
244
|
+
agent.role || 'worker',
|
|
245
|
+
agent.status || 'idle',
|
|
246
|
+
String(agent.iteration || 0),
|
|
247
|
+
agent.cpu || '0.0%',
|
|
248
|
+
agent.memory || '0 MB',
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
agentTable.setData({
|
|
252
|
+
headers: ['Cluster ID', 'Agent ID', 'Role', 'Status', 'Iter', 'CPU', 'Memory'],
|
|
253
|
+
data,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Update system stats box with current metrics
|
|
259
|
+
* @param {object} statsBox - Stats box widget
|
|
260
|
+
* @param {object} stats - Object with properties: totalMemory, usedMemory, totalCPU, activeClusters, totalAgents
|
|
261
|
+
*/
|
|
262
|
+
function updateStatsBox(statsBox, stats) {
|
|
263
|
+
const content =
|
|
264
|
+
`{cyan-fg}Active Clusters:{/}\n` +
|
|
265
|
+
` {white-fg}${stats.activeClusters || 0}{/}\n\n` +
|
|
266
|
+
`{cyan-fg}Total Agents:{/}\n` +
|
|
267
|
+
` {white-fg}${stats.totalAgents || 0}{/}\n\n` +
|
|
268
|
+
`{cyan-fg}System Memory:{/}\n` +
|
|
269
|
+
` {white-fg}${stats.usedMemory || '0 MB'}{/}\n` +
|
|
270
|
+
` {gray-fg}/ ${stats.totalMemory || '0 MB'}{/}\n\n` +
|
|
271
|
+
`{cyan-fg}System CPU:{/}\n` +
|
|
272
|
+
` {white-fg}${stats.totalCPU || '0.0%'}{/}`;
|
|
273
|
+
|
|
274
|
+
statsBox.setContent(content);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Add log entry to logs widget
|
|
279
|
+
* @param {object} logsBox - Logs box widget
|
|
280
|
+
* @param {string} message - Log message
|
|
281
|
+
* @param {string} level - Log level (info, warn, error, debug)
|
|
282
|
+
*/
|
|
283
|
+
function addLogEntry(logsBox, message, level = 'info') {
|
|
284
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
285
|
+
const levelColor = {
|
|
286
|
+
info: 'white-fg',
|
|
287
|
+
warn: 'yellow-fg',
|
|
288
|
+
error: 'red-fg',
|
|
289
|
+
debug: 'gray-fg',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const color = levelColor[level] || 'white-fg';
|
|
293
|
+
const logMessage = `{${color}}[${timestamp}]{/} ${message}`;
|
|
294
|
+
|
|
295
|
+
logsBox.log(logMessage);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Clear logs widget by resetting content
|
|
300
|
+
* @param {object} logsBox - Logs box widget
|
|
301
|
+
*/
|
|
302
|
+
function clearLogs(logsBox) {
|
|
303
|
+
// Log widget doesn't have clearData, so we destroy and recreate
|
|
304
|
+
// Or use native content clearing
|
|
305
|
+
if (logsBox._logLines) {
|
|
306
|
+
logsBox._logLines = [];
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
module.exports = {
|
|
311
|
+
createLayout,
|
|
312
|
+
updateClustersTable,
|
|
313
|
+
updateAgentsTable,
|
|
314
|
+
updateStatsBox,
|
|
315
|
+
addLogEntry,
|
|
316
|
+
clearLogs,
|
|
317
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Screen Renderer
|
|
3
|
+
* Transforms polled data into widget updates using formatters and layout widgets
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { formatTimestamp, formatBytes, formatCPU, stateIcon, truncate } = require('./formatters');
|
|
7
|
+
|
|
8
|
+
class Renderer {
|
|
9
|
+
/**
|
|
10
|
+
* Create renderer instance
|
|
11
|
+
* @param {object} widgets - Widget objects from layout.js
|
|
12
|
+
* @param {object} screen - Blessed screen instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(widgets, screen) {
|
|
15
|
+
if (!widgets) {
|
|
16
|
+
throw new Error('Renderer requires widgets object from layout');
|
|
17
|
+
}
|
|
18
|
+
if (!screen) {
|
|
19
|
+
throw new Error('Renderer requires screen instance');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.widgets = widgets;
|
|
23
|
+
this.screen = screen;
|
|
24
|
+
this.selectedClusterId = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the currently selected cluster ID
|
|
29
|
+
* @param {string|null} id - Cluster ID to select
|
|
30
|
+
*/
|
|
31
|
+
setSelectedCluster(id) {
|
|
32
|
+
this.selectedClusterId = id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Render clusters table with state icons and uptime
|
|
37
|
+
* @param {Array} clusters - Array of cluster objects
|
|
38
|
+
*/
|
|
39
|
+
renderClustersTable(clusters) {
|
|
40
|
+
if (!clusters || !Array.isArray(clusters)) {
|
|
41
|
+
clusters = [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = clusters.map((c) => {
|
|
45
|
+
if (!c) return ['', '', '', ''];
|
|
46
|
+
|
|
47
|
+
const icon = stateIcon(c.state || 'unknown');
|
|
48
|
+
const uptime =
|
|
49
|
+
c.state === 'running' && c.createdAt ? formatTimestamp(Date.now() - c.createdAt) : '-';
|
|
50
|
+
const clusterId = truncate(c.id || '', 18);
|
|
51
|
+
const state = (c.state || 'unknown').toUpperCase();
|
|
52
|
+
const agentCount = `${c.agentCount || 0} agents`;
|
|
53
|
+
|
|
54
|
+
return [`${icon} ${clusterId}`, state, agentCount, uptime];
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (this.widgets.clustersTable && this.widgets.clustersTable.setData) {
|
|
58
|
+
this.widgets.clustersTable.setData({
|
|
59
|
+
headers: ['ID', 'State', 'Agents', 'Uptime'],
|
|
60
|
+
data,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Render system statistics box with aggregate metrics
|
|
67
|
+
* @param {Array} clusters - Array of cluster objects
|
|
68
|
+
* @param {Map} resourceStats - Map of PID -> {cpu, memory}
|
|
69
|
+
*/
|
|
70
|
+
renderSystemStats(clusters, resourceStats) {
|
|
71
|
+
if (!clusters || !Array.isArray(clusters)) {
|
|
72
|
+
clusters = [];
|
|
73
|
+
}
|
|
74
|
+
if (!resourceStats || !(resourceStats instanceof Map)) {
|
|
75
|
+
resourceStats = new Map();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Calculate aggregate stats
|
|
79
|
+
const activeClusters = clusters.filter((c) => c && c.state === 'running').length;
|
|
80
|
+
const totalAgents = clusters.reduce((sum, c) => sum + (c?.agentCount || 0), 0);
|
|
81
|
+
|
|
82
|
+
// Calculate average CPU and memory from resource stats
|
|
83
|
+
let totalCpu = 0;
|
|
84
|
+
let totalMemory = 0;
|
|
85
|
+
let statCount = 0;
|
|
86
|
+
|
|
87
|
+
resourceStats.forEach((stat) => {
|
|
88
|
+
if (stat && typeof stat.cpu === 'number' && typeof stat.memory === 'number') {
|
|
89
|
+
totalCpu += stat.cpu;
|
|
90
|
+
totalMemory += stat.memory;
|
|
91
|
+
statCount++;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const avgCpu = statCount > 0 ? totalCpu / statCount : 0;
|
|
96
|
+
const avgMemory = statCount > 0 ? totalMemory / statCount : 0;
|
|
97
|
+
|
|
98
|
+
// Format output with blessed color tags
|
|
99
|
+
const statsText = [
|
|
100
|
+
'{cyan-fg}Active Clusters:{/} ' + activeClusters,
|
|
101
|
+
'{cyan-fg}Total Agents:{/} ' + totalAgents,
|
|
102
|
+
'{cyan-fg}Avg CPU:{/} ' + formatCPU(avgCpu),
|
|
103
|
+
'{cyan-fg}Avg Memory:{/} ' + formatBytes(avgMemory),
|
|
104
|
+
].join('\n');
|
|
105
|
+
|
|
106
|
+
if (this.widgets.statsBox && this.widgets.statsBox.setContent) {
|
|
107
|
+
this.widgets.statsBox.setContent(statsText);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Render agent table for selected cluster
|
|
113
|
+
* @param {Array} agents - Array of agent objects
|
|
114
|
+
* @param {Map} resourceStats - Map of PID -> {cpu, memory}
|
|
115
|
+
*/
|
|
116
|
+
renderAgentTable(agents, resourceStats) {
|
|
117
|
+
if (!this.selectedClusterId) {
|
|
118
|
+
// No cluster selected, show empty table
|
|
119
|
+
if (this.widgets.agentTable && this.widgets.agentTable.setData) {
|
|
120
|
+
this.widgets.agentTable.setData({
|
|
121
|
+
headers: ['Agent', 'Role', 'State', 'Iter', 'CPU%', 'Mem'],
|
|
122
|
+
data: [],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!agents || !Array.isArray(agents)) {
|
|
129
|
+
agents = [];
|
|
130
|
+
}
|
|
131
|
+
if (!resourceStats || !(resourceStats instanceof Map)) {
|
|
132
|
+
resourceStats = new Map();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const data = agents.map((a) => {
|
|
136
|
+
if (!a) return ['', '', '', '', '', ''];
|
|
137
|
+
|
|
138
|
+
const pid = a.pid;
|
|
139
|
+
const stats = resourceStats.get(pid) || { cpu: 0, memory: 0 };
|
|
140
|
+
|
|
141
|
+
const agentId = truncate(a.id || '', 12);
|
|
142
|
+
const role = truncate(a.role || '', 12);
|
|
143
|
+
const state = a.state || 'unknown';
|
|
144
|
+
const iteration = `${a.iteration || 0}/${a.maxIterations || 0}`;
|
|
145
|
+
const cpu = formatCPU(stats.cpu);
|
|
146
|
+
const memory = formatBytes(stats.memory);
|
|
147
|
+
|
|
148
|
+
return [agentId, role, state, iteration, cpu, memory];
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (this.widgets.agentTable && this.widgets.agentTable.setData) {
|
|
152
|
+
this.widgets.agentTable.setData({
|
|
153
|
+
headers: ['Agent', 'Role', 'State', 'Iter', 'CPU%', 'Mem'],
|
|
154
|
+
data,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Render log messages to log widget
|
|
161
|
+
* @param {Array} messages - Array of message objects
|
|
162
|
+
*/
|
|
163
|
+
renderLogs(messages) {
|
|
164
|
+
if (!messages || !Array.isArray(messages)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!this.widgets.logsBox || !this.widgets.logsBox.log) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
messages.forEach((msg) => {
|
|
173
|
+
if (!msg) return;
|
|
174
|
+
|
|
175
|
+
const timestamp = msg.timestamp || Date.now();
|
|
176
|
+
const time = new Date(timestamp).toLocaleTimeString();
|
|
177
|
+
const sender = truncate(msg.sender || 'unknown', 15);
|
|
178
|
+
const text = truncate(msg.content?.text || '', 60);
|
|
179
|
+
|
|
180
|
+
this.widgets.logsBox.log(`[${time}] ${sender}: ${text}`);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Trigger screen render to update display
|
|
186
|
+
*/
|
|
187
|
+
render() {
|
|
188
|
+
if (this.screen && this.screen.render) {
|
|
189
|
+
this.screen.render();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = Renderer;
|