@stackmemoryai/stackmemory 0.3.26 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/ralph.js +125 -188
- package/dist/cli/commands/ralph.js.map +2 -2
- package/dist/features/tui/simple-monitor.js +112 -0
- package/dist/features/tui/simple-monitor.js.map +7 -0
- package/dist/features/tui/swarm-monitor.js +644 -0
- package/dist/features/tui/swarm-monitor.js.map +7 -0
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js +254 -43
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js.map +3 -3
- package/dist/integrations/ralph/coordination/enhanced-coordination.js +406 -0
- package/dist/integrations/ralph/coordination/enhanced-coordination.js.map +7 -0
- package/dist/integrations/ralph/monitoring/swarm-dashboard.js +290 -0
- package/dist/integrations/ralph/monitoring/swarm-dashboard.js.map +7 -0
- package/dist/integrations/ralph/monitoring/swarm-registry.js +95 -0
- package/dist/integrations/ralph/monitoring/swarm-registry.js.map +7 -0
- package/dist/integrations/ralph/recovery/crash-recovery.js +458 -0
- package/dist/integrations/ralph/recovery/crash-recovery.js.map +7 -0
- package/dist/integrations/ralph/swarm/git-workflow-manager.js +6 -67
- package/dist/integrations/ralph/swarm/git-workflow-manager.js.map +2 -2
- package/dist/integrations/ralph/swarm/swarm-coordinator.js +5 -139
- package/dist/integrations/ralph/swarm/swarm-coordinator.js.map +2 -2
- package/package.json +2 -1
- package/scripts/test-ralph-iteration-fix.ts +118 -0
- package/scripts/test-simple-ralph-state-sync.ts +178 -0
- package/scripts/test-swarm-tui.ts +34 -0
- package/scripts/test-tui-shortcuts.ts +66 -0
- package/scripts/validate-tui-shortcuts.ts +83 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import blessed from "blessed";
|
|
2
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
3
|
+
import { SwarmDashboard } from "../../integrations/ralph/monitoring/swarm-dashboard.js";
|
|
4
|
+
import { SwarmRegistry } from "../../integrations/ralph/monitoring/swarm-registry.js";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
class SwarmTUI {
|
|
7
|
+
screen;
|
|
8
|
+
commitsTable;
|
|
9
|
+
statusBox;
|
|
10
|
+
agentsTable;
|
|
11
|
+
metricsBox;
|
|
12
|
+
logBox;
|
|
13
|
+
swarmCoordinator = null;
|
|
14
|
+
swarmDashboard = null;
|
|
15
|
+
refreshInterval = null;
|
|
16
|
+
commitMetrics = /* @__PURE__ */ new Map();
|
|
17
|
+
constructor() {
|
|
18
|
+
const isGhostty = process.env.TERM_PROGRAM === "ghostty" || process.env.TERM?.includes("ghostty");
|
|
19
|
+
const isBasicTerm = process.env.TERM === "dumb" || process.env.TERM === "unknown";
|
|
20
|
+
this.screen = blessed.screen({
|
|
21
|
+
smartCSR: !isGhostty,
|
|
22
|
+
// Disable smart CSR for ghostty
|
|
23
|
+
title: "Ralph Swarm Monitor",
|
|
24
|
+
terminal: isGhostty ? "xterm-256color" : void 0,
|
|
25
|
+
fullUnicode: !isBasicTerm,
|
|
26
|
+
dockBorders: false,
|
|
27
|
+
ignoreDockContrast: true,
|
|
28
|
+
useBCE: false,
|
|
29
|
+
// Disable background color erase for compatibility
|
|
30
|
+
forceUnicode: false,
|
|
31
|
+
debug: false
|
|
32
|
+
});
|
|
33
|
+
this.screen.on("error", (err) => {
|
|
34
|
+
logger.error("TUI screen error:", err);
|
|
35
|
+
console.log(
|
|
36
|
+
"\u26A0\uFE0F TUI display error detected. Try setting TERM=xterm-256color"
|
|
37
|
+
);
|
|
38
|
+
console.log(
|
|
39
|
+
" Alternative: Use stackmemory ralph status for text-based monitoring"
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
this.createUI();
|
|
43
|
+
this.setupKeyHandlers();
|
|
44
|
+
logger.info("SwarmTUI initialized");
|
|
45
|
+
}
|
|
46
|
+
createUI() {
|
|
47
|
+
const container = blessed.box({
|
|
48
|
+
parent: this.screen,
|
|
49
|
+
top: 0,
|
|
50
|
+
left: 0,
|
|
51
|
+
width: "100%",
|
|
52
|
+
height: "100%",
|
|
53
|
+
style: {
|
|
54
|
+
bg: "black"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const isGhostty = process.env.TERM_PROGRAM === "ghostty" || process.env.TERM?.includes("ghostty");
|
|
58
|
+
const safeColors = isGhostty;
|
|
59
|
+
blessed.box({
|
|
60
|
+
parent: container,
|
|
61
|
+
top: 0,
|
|
62
|
+
left: 0,
|
|
63
|
+
width: "100%",
|
|
64
|
+
height: 3,
|
|
65
|
+
content: "\u{1F9BE} Ralph Swarm Monitor - Real-time Swarm Operations",
|
|
66
|
+
style: safeColors ? {
|
|
67
|
+
fg: "white",
|
|
68
|
+
bold: false
|
|
69
|
+
} : {
|
|
70
|
+
bg: "blue",
|
|
71
|
+
fg: "white",
|
|
72
|
+
bold: true
|
|
73
|
+
},
|
|
74
|
+
border: {
|
|
75
|
+
type: "line"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
this.statusBox = blessed.box({
|
|
79
|
+
parent: container,
|
|
80
|
+
top: 3,
|
|
81
|
+
left: "50%",
|
|
82
|
+
width: "50%",
|
|
83
|
+
height: 8,
|
|
84
|
+
label: " Swarm Status ",
|
|
85
|
+
content: "No active swarm",
|
|
86
|
+
style: {
|
|
87
|
+
bg: "black",
|
|
88
|
+
fg: "white"
|
|
89
|
+
},
|
|
90
|
+
border: {
|
|
91
|
+
type: "line",
|
|
92
|
+
fg: "cyan"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
this.metricsBox = blessed.box({
|
|
96
|
+
parent: container,
|
|
97
|
+
top: 3,
|
|
98
|
+
left: 0,
|
|
99
|
+
width: "50%",
|
|
100
|
+
height: 8,
|
|
101
|
+
label: " Performance Metrics ",
|
|
102
|
+
content: "Waiting for data...",
|
|
103
|
+
style: {
|
|
104
|
+
bg: "black",
|
|
105
|
+
fg: "white"
|
|
106
|
+
},
|
|
107
|
+
border: {
|
|
108
|
+
type: "line",
|
|
109
|
+
fg: "green"
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
this.agentsTable = blessed.table({
|
|
113
|
+
parent: container,
|
|
114
|
+
top: 11,
|
|
115
|
+
left: 0,
|
|
116
|
+
width: "50%",
|
|
117
|
+
height: 12,
|
|
118
|
+
label: " Active Agents ",
|
|
119
|
+
style: {
|
|
120
|
+
bg: "black",
|
|
121
|
+
fg: "white",
|
|
122
|
+
header: {
|
|
123
|
+
bg: "blue",
|
|
124
|
+
fg: "white",
|
|
125
|
+
bold: true
|
|
126
|
+
},
|
|
127
|
+
cell: {
|
|
128
|
+
selected: {
|
|
129
|
+
bg: "blue",
|
|
130
|
+
fg: "white"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
border: {
|
|
135
|
+
type: "line",
|
|
136
|
+
fg: "yellow"
|
|
137
|
+
},
|
|
138
|
+
data: [["Role", "Status", "Iteration", "Task", "Last Active"]]
|
|
139
|
+
});
|
|
140
|
+
this.commitsTable = blessed.table({
|
|
141
|
+
parent: container,
|
|
142
|
+
top: 11,
|
|
143
|
+
left: "50%",
|
|
144
|
+
width: "50%",
|
|
145
|
+
height: 12,
|
|
146
|
+
label: " Recent Commits ",
|
|
147
|
+
style: {
|
|
148
|
+
bg: "black",
|
|
149
|
+
fg: "white",
|
|
150
|
+
header: {
|
|
151
|
+
bg: "blue",
|
|
152
|
+
fg: "white",
|
|
153
|
+
bold: true
|
|
154
|
+
},
|
|
155
|
+
cell: {
|
|
156
|
+
selected: {
|
|
157
|
+
bg: "blue",
|
|
158
|
+
fg: "white"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
border: {
|
|
163
|
+
type: "line",
|
|
164
|
+
fg: "magenta"
|
|
165
|
+
},
|
|
166
|
+
data: [["Agent", "Message", "Lines +/-", "Time"]]
|
|
167
|
+
});
|
|
168
|
+
this.logBox = blessed.log({
|
|
169
|
+
parent: container,
|
|
170
|
+
top: 23,
|
|
171
|
+
left: 0,
|
|
172
|
+
width: "100%",
|
|
173
|
+
height: "100%-23",
|
|
174
|
+
label: " Swarm Logs ",
|
|
175
|
+
style: {
|
|
176
|
+
bg: "black",
|
|
177
|
+
fg: "white"
|
|
178
|
+
},
|
|
179
|
+
border: {
|
|
180
|
+
type: "line",
|
|
181
|
+
fg: "white"
|
|
182
|
+
},
|
|
183
|
+
scrollable: true,
|
|
184
|
+
alwaysScroll: true,
|
|
185
|
+
mouse: true
|
|
186
|
+
});
|
|
187
|
+
blessed.box({
|
|
188
|
+
parent: container,
|
|
189
|
+
bottom: 0,
|
|
190
|
+
left: 0,
|
|
191
|
+
width: "100%",
|
|
192
|
+
height: 1,
|
|
193
|
+
content: "q=quit | r=refresh | s=start swarm | t=stop swarm | h=help | c=clear logs | d=detect swarms",
|
|
194
|
+
style: {
|
|
195
|
+
bg: "white",
|
|
196
|
+
fg: "black"
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
setupKeyHandlers() {
|
|
201
|
+
this.screen.key(["escape", "q", "C-c"], () => {
|
|
202
|
+
this.cleanup();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
});
|
|
205
|
+
this.screen.key(["r"], () => {
|
|
206
|
+
this.refreshData();
|
|
207
|
+
this.logBox.log("Manual refresh triggered");
|
|
208
|
+
});
|
|
209
|
+
this.screen.key(["s"], () => {
|
|
210
|
+
this.startSwarmInteractive();
|
|
211
|
+
});
|
|
212
|
+
this.screen.key(["t"], () => {
|
|
213
|
+
this.stopSwarmInteractive();
|
|
214
|
+
});
|
|
215
|
+
this.screen.key(["h"], () => {
|
|
216
|
+
this.showHelp();
|
|
217
|
+
});
|
|
218
|
+
this.screen.key(["c"], () => {
|
|
219
|
+
this.clearLogs();
|
|
220
|
+
});
|
|
221
|
+
this.screen.key(["d"], () => {
|
|
222
|
+
this.showDetectedSwarms();
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Initialize swarm monitoring
|
|
227
|
+
*/
|
|
228
|
+
async initialize(swarmCoordinator, swarmId) {
|
|
229
|
+
try {
|
|
230
|
+
if (swarmId) {
|
|
231
|
+
const registry = SwarmRegistry.getInstance();
|
|
232
|
+
const swarm = registry.getSwarm(swarmId);
|
|
233
|
+
if (swarm) {
|
|
234
|
+
this.swarmCoordinator = swarm.coordinator;
|
|
235
|
+
this.logBox.log(`Connected to swarm: ${swarmId}`);
|
|
236
|
+
} else {
|
|
237
|
+
this.logBox.log(`Swarm not found: ${swarmId}`);
|
|
238
|
+
}
|
|
239
|
+
} else if (swarmCoordinator) {
|
|
240
|
+
this.swarmCoordinator = swarmCoordinator;
|
|
241
|
+
} else {
|
|
242
|
+
const registry = SwarmRegistry.getInstance();
|
|
243
|
+
const activeSwarms = registry.listActiveSwarms();
|
|
244
|
+
if (activeSwarms.length > 0) {
|
|
245
|
+
this.swarmCoordinator = activeSwarms[0].coordinator;
|
|
246
|
+
this.logBox.log(`Auto-connected to swarm: ${activeSwarms[0].id}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (this.swarmCoordinator) {
|
|
250
|
+
this.swarmDashboard = new SwarmDashboard(this.swarmCoordinator);
|
|
251
|
+
this.swarmDashboard.startMonitoring(2e3);
|
|
252
|
+
this.swarmDashboard.on("metricsUpdated", (metrics) => {
|
|
253
|
+
this.updateUI(metrics);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
this.refreshInterval = setInterval(() => {
|
|
257
|
+
this.refreshData();
|
|
258
|
+
}, 3e3);
|
|
259
|
+
this.logBox.log("SwarmTUI monitoring initialized");
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error("Failed to initialize SwarmTUI", error);
|
|
262
|
+
this.logBox.log(`Error: ${error.message}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Start the TUI display
|
|
267
|
+
*/
|
|
268
|
+
start() {
|
|
269
|
+
this.screen.render();
|
|
270
|
+
this.logBox.log("Ralph Swarm Monitor started");
|
|
271
|
+
this.logBox.log("Monitoring for active swarms...");
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Refresh all data
|
|
275
|
+
*/
|
|
276
|
+
async refreshData() {
|
|
277
|
+
try {
|
|
278
|
+
await this.updateCommitMetrics();
|
|
279
|
+
if (this.swarmCoordinator) {
|
|
280
|
+
const status = this.getSwarmStatus();
|
|
281
|
+
this.updateStatusDisplay(status);
|
|
282
|
+
} else {
|
|
283
|
+
await this.detectActiveSwarms();
|
|
284
|
+
}
|
|
285
|
+
this.screen.render();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
logger.error("Failed to refresh TUI data", error);
|
|
288
|
+
this.logBox.log(`Refresh error: ${error.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Update commit metrics for all agents
|
|
293
|
+
*/
|
|
294
|
+
async updateCommitMetrics() {
|
|
295
|
+
try {
|
|
296
|
+
const gitLog = execSync(
|
|
297
|
+
'git log --oneline --since="1 hour ago" --pretty=format:"%H|%an|%s|%ct" --numstat',
|
|
298
|
+
{ encoding: "utf8", cwd: process.cwd() }
|
|
299
|
+
);
|
|
300
|
+
const commits = this.parseGitCommits(gitLog);
|
|
301
|
+
this.updateCommitsTable(commits);
|
|
302
|
+
} catch {
|
|
303
|
+
this.logBox.log("No recent commits found");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Parse git log output into commit data
|
|
308
|
+
*/
|
|
309
|
+
parseGitCommits(gitLog) {
|
|
310
|
+
const commits = [];
|
|
311
|
+
const lines = gitLog.split("\n").filter(Boolean);
|
|
312
|
+
let currentCommit = null;
|
|
313
|
+
for (const line of lines) {
|
|
314
|
+
if (line.includes("|")) {
|
|
315
|
+
const [hash, author, message, timestamp] = line.split("|");
|
|
316
|
+
currentCommit = {
|
|
317
|
+
hash: hash.substring(0, 8),
|
|
318
|
+
agent: this.extractAgentFromAuthor(author),
|
|
319
|
+
message: message.substring(0, 50),
|
|
320
|
+
timestamp: parseInt(timestamp),
|
|
321
|
+
linesAdded: 0,
|
|
322
|
+
linesDeleted: 0
|
|
323
|
+
};
|
|
324
|
+
} else if (currentCommit && line.match(/^\d+\s+\d+/)) {
|
|
325
|
+
const [added, deleted] = line.split(" ")[0].split(" ");
|
|
326
|
+
currentCommit.linesAdded += parseInt(added) || 0;
|
|
327
|
+
currentCommit.linesDeleted += parseInt(deleted) || 0;
|
|
328
|
+
commits.push({ ...currentCommit });
|
|
329
|
+
currentCommit = null;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return commits.slice(0, 10);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Extract agent info from git author
|
|
336
|
+
*/
|
|
337
|
+
extractAgentFromAuthor(author) {
|
|
338
|
+
const agentMatch = author.match(/\[(\w+)\]/);
|
|
339
|
+
if (agentMatch) {
|
|
340
|
+
return agentMatch[1];
|
|
341
|
+
}
|
|
342
|
+
const roles = [
|
|
343
|
+
"developer",
|
|
344
|
+
"tester",
|
|
345
|
+
"optimizer",
|
|
346
|
+
"documenter",
|
|
347
|
+
"architect"
|
|
348
|
+
];
|
|
349
|
+
for (const role of roles) {
|
|
350
|
+
if (author.toLowerCase().includes(role)) {
|
|
351
|
+
return role;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return "user";
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Update commits table display
|
|
358
|
+
*/
|
|
359
|
+
updateCommitsTable(commits) {
|
|
360
|
+
const tableData = [["Agent", "Message", "Lines +/-", "Time"]];
|
|
361
|
+
for (const commit of commits) {
|
|
362
|
+
const timeAgo = this.formatTimeAgo(commit.timestamp * 1e3);
|
|
363
|
+
const linesChange = `+${commit.linesAdded}/-${commit.linesDeleted}`;
|
|
364
|
+
tableData.push([commit.agent, commit.message, linesChange, timeAgo]);
|
|
365
|
+
}
|
|
366
|
+
this.commitsTable.setData(tableData);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Get current swarm status
|
|
370
|
+
*/
|
|
371
|
+
getSwarmStatus() {
|
|
372
|
+
if (!this.swarmCoordinator) return null;
|
|
373
|
+
const usage = this.swarmCoordinator.getResourceUsage();
|
|
374
|
+
const swarmState = this.swarmCoordinator.swarmState;
|
|
375
|
+
if (!swarmState) return null;
|
|
376
|
+
return {
|
|
377
|
+
swarmId: swarmState.id,
|
|
378
|
+
status: swarmState.status,
|
|
379
|
+
startTime: swarmState.startTime,
|
|
380
|
+
uptime: Date.now() - swarmState.startTime,
|
|
381
|
+
agents: usage.activeAgents ? Array.from(
|
|
382
|
+
this.swarmCoordinator.activeAgents?.values() || []
|
|
383
|
+
).map((agent) => ({
|
|
384
|
+
id: agent.id,
|
|
385
|
+
role: agent.role,
|
|
386
|
+
status: agent.status,
|
|
387
|
+
currentTask: agent.currentTask,
|
|
388
|
+
iteration: agent.performance?.tasksCompleted || 0,
|
|
389
|
+
lastActivity: agent.performance?.lastFreshStart || Date.now()
|
|
390
|
+
})) : [],
|
|
391
|
+
performance: {
|
|
392
|
+
throughput: swarmState.performance?.throughput || 0,
|
|
393
|
+
efficiency: swarmState.performance?.efficiency || 0,
|
|
394
|
+
totalTasks: swarmState.totalTaskCount || 0,
|
|
395
|
+
completedTasks: swarmState.completedTaskCount || 0
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Update status display
|
|
401
|
+
*/
|
|
402
|
+
updateStatusDisplay(status) {
|
|
403
|
+
if (!status) {
|
|
404
|
+
this.statusBox.setContent("No active swarm detected");
|
|
405
|
+
this.agentsTable.setData([
|
|
406
|
+
["Role", "Status", "Iteration", "Task", "Last Active"]
|
|
407
|
+
]);
|
|
408
|
+
this.metricsBox.setContent("Waiting for swarm data...");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const uptimeStr = this.formatDuration(status.uptime);
|
|
412
|
+
const statusContent = `Swarm: ${status.swarmId.substring(0, 8)}
|
|
413
|
+
Status: ${status.status.toUpperCase()}
|
|
414
|
+
Uptime: ${uptimeStr}
|
|
415
|
+
Agents: ${status.agents.length}`;
|
|
416
|
+
this.statusBox.setContent(statusContent);
|
|
417
|
+
const agentData = [["Role", "Status", "Iteration", "Task", "Last Active"]];
|
|
418
|
+
for (const agent of status.agents) {
|
|
419
|
+
const lastActivity = this.formatTimeAgo(agent.lastActivity);
|
|
420
|
+
const task = agent.currentTask ? agent.currentTask.substring(0, 20) : "idle";
|
|
421
|
+
agentData.push([
|
|
422
|
+
agent.role,
|
|
423
|
+
agent.status,
|
|
424
|
+
agent.iteration.toString(),
|
|
425
|
+
task,
|
|
426
|
+
lastActivity
|
|
427
|
+
]);
|
|
428
|
+
}
|
|
429
|
+
this.agentsTable.setData(agentData);
|
|
430
|
+
const metricsContent = `Throughput: ${status.performance.throughput.toFixed(2)} tasks/min
|
|
431
|
+
Efficiency: ${(status.performance.efficiency * 100).toFixed(1)}%
|
|
432
|
+
Tasks: ${status.performance.completedTasks}/${status.performance.totalTasks}
|
|
433
|
+
Success Rate: ${status.performance.efficiency > 0 ? (status.performance.efficiency * 100).toFixed(1) : "N/A"}%`;
|
|
434
|
+
this.metricsBox.setContent(metricsContent);
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Update UI with metrics from dashboard
|
|
438
|
+
*/
|
|
439
|
+
updateUI(metrics) {
|
|
440
|
+
this.logBox.log(
|
|
441
|
+
`Metrics updated: ${metrics.status} - ${metrics.activeAgents} agents`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Detect active swarms in the system
|
|
446
|
+
*/
|
|
447
|
+
async detectActiveSwarms() {
|
|
448
|
+
try {
|
|
449
|
+
const registry = SwarmRegistry.getInstance();
|
|
450
|
+
const activeSwarms = registry.listActiveSwarms();
|
|
451
|
+
const stats = registry.getStatistics();
|
|
452
|
+
if (activeSwarms.length > 0) {
|
|
453
|
+
let statusContent = `Available Swarms (${activeSwarms.length}):
|
|
454
|
+
`;
|
|
455
|
+
for (const swarm of activeSwarms.slice(0, 3)) {
|
|
456
|
+
const uptime = this.formatDuration(Date.now() - swarm.startTime);
|
|
457
|
+
statusContent += `\u2022 ${swarm.id.substring(0, 8)}: ${swarm.status} (${uptime})
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
460
|
+
if (activeSwarms.length > 3) {
|
|
461
|
+
statusContent += `... and ${activeSwarms.length - 3} more`;
|
|
462
|
+
}
|
|
463
|
+
this.statusBox.setContent(statusContent);
|
|
464
|
+
this.logBox.log(
|
|
465
|
+
`Found ${activeSwarms.length} active swarms in registry`
|
|
466
|
+
);
|
|
467
|
+
} else {
|
|
468
|
+
const ralphProcesses = execSync(
|
|
469
|
+
'ps aux | grep "ralph" | grep -v grep',
|
|
470
|
+
{ encoding: "utf8" }
|
|
471
|
+
);
|
|
472
|
+
if (ralphProcesses.trim()) {
|
|
473
|
+
this.logBox.log("Detected Ralph processes running");
|
|
474
|
+
this.statusBox.setContent(
|
|
475
|
+
"External Ralph processes detected\n(Use swarm coordinator for full monitoring)"
|
|
476
|
+
);
|
|
477
|
+
} else {
|
|
478
|
+
this.statusBox.setContent(`No active swarms detected
|
|
479
|
+
Total swarms: ${stats.totalSwarms}
|
|
480
|
+
Completed: ${stats.completedSwarms}
|
|
481
|
+
|
|
482
|
+
Run: stackmemory ralph swarm <task>`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Format time ago string
|
|
490
|
+
*/
|
|
491
|
+
formatTimeAgo(timestamp) {
|
|
492
|
+
const diff = Date.now() - timestamp;
|
|
493
|
+
const minutes = Math.floor(diff / 6e4);
|
|
494
|
+
const hours = Math.floor(minutes / 60);
|
|
495
|
+
const days = Math.floor(hours / 24);
|
|
496
|
+
if (days > 0) return `${days}d ago`;
|
|
497
|
+
if (hours > 0) return `${hours}h ago`;
|
|
498
|
+
if (minutes > 0) return `${minutes}m ago`;
|
|
499
|
+
return "just now";
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Format duration string
|
|
503
|
+
*/
|
|
504
|
+
formatDuration(ms) {
|
|
505
|
+
const seconds = Math.floor(ms / 1e3);
|
|
506
|
+
const minutes = Math.floor(seconds / 60);
|
|
507
|
+
const hours = Math.floor(minutes / 60);
|
|
508
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
509
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
510
|
+
return `${seconds}s`;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Start swarm interactively
|
|
514
|
+
*/
|
|
515
|
+
startSwarmInteractive() {
|
|
516
|
+
this.logBox.log("\u{1F680} Start Swarm Interactive Mode:");
|
|
517
|
+
this.logBox.log(
|
|
518
|
+
'Example: stackmemory ralph swarm "Implement feature" --agents developer,tester'
|
|
519
|
+
);
|
|
520
|
+
this.logBox.log(
|
|
521
|
+
'Tip: Run the command in another terminal, then press "d" to detect it'
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Stop swarm interactively
|
|
526
|
+
*/
|
|
527
|
+
stopSwarmInteractive() {
|
|
528
|
+
if (this.swarmCoordinator) {
|
|
529
|
+
this.logBox.log("\u{1F6D1} Stopping current swarm...");
|
|
530
|
+
this.logBox.log(
|
|
531
|
+
"Note: Swarm stopping not yet implemented - use Ctrl+C in swarm terminal"
|
|
532
|
+
);
|
|
533
|
+
} else {
|
|
534
|
+
this.logBox.log("\u274C No active swarm coordinator to stop");
|
|
535
|
+
this.logBox.log("External Ralph processes must be stopped manually");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Show help dialog
|
|
540
|
+
*/
|
|
541
|
+
showHelp() {
|
|
542
|
+
this.logBox.log("\u{1F9BE} Ralph Swarm Monitor - Help");
|
|
543
|
+
this.logBox.log("");
|
|
544
|
+
this.logBox.log("Keyboard Shortcuts:");
|
|
545
|
+
this.logBox.log(" q, Esc, Ctrl+C - Quit TUI");
|
|
546
|
+
this.logBox.log(" r - Refresh data manually");
|
|
547
|
+
this.logBox.log(" s - Show start swarm commands");
|
|
548
|
+
this.logBox.log(" t - Stop current swarm");
|
|
549
|
+
this.logBox.log(" h - Show this help");
|
|
550
|
+
this.logBox.log(" c - Clear log output");
|
|
551
|
+
this.logBox.log(" d - Detect and list available swarms");
|
|
552
|
+
this.logBox.log("");
|
|
553
|
+
this.logBox.log("Usage:");
|
|
554
|
+
this.logBox.log(
|
|
555
|
+
" stackmemory ralph tui # Auto-detect swarms"
|
|
556
|
+
);
|
|
557
|
+
this.logBox.log(
|
|
558
|
+
" stackmemory ralph tui --swarm-id <id> # Monitor specific swarm"
|
|
559
|
+
);
|
|
560
|
+
this.logBox.log("");
|
|
561
|
+
this.logBox.log("Starting Swarms:");
|
|
562
|
+
this.logBox.log(
|
|
563
|
+
' stackmemory ralph swarm "Task description" --agents developer,tester'
|
|
564
|
+
);
|
|
565
|
+
this.logBox.log("");
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Clear log output
|
|
569
|
+
*/
|
|
570
|
+
clearLogs() {
|
|
571
|
+
this.logBox.setContent("");
|
|
572
|
+
this.logBox.log("\u{1F4DD} Logs cleared - monitoring continues...");
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Show detected swarms
|
|
576
|
+
*/
|
|
577
|
+
async showDetectedSwarms() {
|
|
578
|
+
this.logBox.log("\u{1F50D} Detecting active swarms...");
|
|
579
|
+
try {
|
|
580
|
+
const registry = SwarmRegistry.getInstance();
|
|
581
|
+
const activeSwarms = registry.listActiveSwarms();
|
|
582
|
+
const stats = registry.getStatistics();
|
|
583
|
+
this.logBox.log("");
|
|
584
|
+
this.logBox.log("\u{1F4CA} Swarm Registry Status:");
|
|
585
|
+
this.logBox.log(` Total swarms: ${stats.totalSwarms}`);
|
|
586
|
+
this.logBox.log(` Active swarms: ${stats.activeSwarms}`);
|
|
587
|
+
this.logBox.log(` Completed swarms: ${stats.completedSwarms}`);
|
|
588
|
+
if (activeSwarms.length > 0) {
|
|
589
|
+
this.logBox.log("");
|
|
590
|
+
this.logBox.log("\u{1F9BE} Active Swarms:");
|
|
591
|
+
for (const swarm of activeSwarms) {
|
|
592
|
+
const uptime = this.formatDuration(Date.now() - swarm.startTime);
|
|
593
|
+
this.logBox.log(` \u2022 ${swarm.id}: ${swarm.description} (${uptime})`);
|
|
594
|
+
}
|
|
595
|
+
this.logBox.log("");
|
|
596
|
+
this.logBox.log("\u{1F4A1} Use --swarm-id to connect to specific swarm");
|
|
597
|
+
} else {
|
|
598
|
+
this.logBox.log("");
|
|
599
|
+
this.logBox.log("\u274C No active swarms in registry");
|
|
600
|
+
try {
|
|
601
|
+
const ralphProcesses = execSync(
|
|
602
|
+
'ps aux | grep "ralph" | grep -v grep',
|
|
603
|
+
{ encoding: "utf8" }
|
|
604
|
+
);
|
|
605
|
+
if (ralphProcesses.trim()) {
|
|
606
|
+
this.logBox.log("\u{1F50D} External Ralph processes detected:");
|
|
607
|
+
ralphProcesses.split("\n").filter((line) => line.trim()).forEach((line) => {
|
|
608
|
+
const parts = line.split(/\s+/);
|
|
609
|
+
this.logBox.log(
|
|
610
|
+
` PID ${parts[1]}: ${parts.slice(10).join(" ").slice(0, 60)}`
|
|
611
|
+
);
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
} catch {
|
|
615
|
+
this.logBox.log("\u{1F50D} No external Ralph processes found");
|
|
616
|
+
}
|
|
617
|
+
this.logBox.log("");
|
|
618
|
+
this.logBox.log(
|
|
619
|
+
'\u{1F4A1} Start a swarm: stackmemory ralph swarm "Task" --agents developer'
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
} catch (error) {
|
|
623
|
+
this.logBox.log(`\u274C Detection failed: ${error.message}`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Cleanup resources
|
|
628
|
+
*/
|
|
629
|
+
cleanup() {
|
|
630
|
+
if (this.refreshInterval) {
|
|
631
|
+
clearInterval(this.refreshInterval);
|
|
632
|
+
}
|
|
633
|
+
if (this.swarmDashboard) {
|
|
634
|
+
this.swarmDashboard.stopMonitoring();
|
|
635
|
+
}
|
|
636
|
+
logger.info("SwarmTUI cleaned up");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
var swarm_monitor_default = SwarmTUI;
|
|
640
|
+
export {
|
|
641
|
+
SwarmTUI,
|
|
642
|
+
swarm_monitor_default as default
|
|
643
|
+
};
|
|
644
|
+
//# sourceMappingURL=swarm-monitor.js.map
|