@stackmemoryai/stackmemory 0.3.21 โ 0.3.24
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/linear-unified.js +2 -3
- package/dist/cli/commands/linear-unified.js.map +2 -2
- package/dist/cli/commands/ralph.js +294 -0
- package/dist/cli/commands/ralph.js.map +7 -0
- package/dist/cli/commands/tasks.js +1 -1
- package/dist/cli/commands/tasks.js.map +2 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +2 -2
- package/dist/integrations/mcp/handlers/code-execution-handlers.js +262 -0
- package/dist/integrations/mcp/handlers/code-execution-handlers.js.map +7 -0
- package/dist/integrations/mcp/tool-definitions-code.js +121 -0
- package/dist/integrations/mcp/tool-definitions-code.js.map +7 -0
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js +586 -0
- package/dist/integrations/ralph/bridge/ralph-stackmemory-bridge.js.map +7 -0
- package/dist/integrations/ralph/context/context-budget-manager.js +297 -0
- package/dist/integrations/ralph/context/context-budget-manager.js.map +7 -0
- package/dist/integrations/ralph/context/stackmemory-context-loader.js +356 -0
- package/dist/integrations/ralph/context/stackmemory-context-loader.js.map +7 -0
- package/dist/integrations/ralph/index.js +14 -0
- package/dist/integrations/ralph/index.js.map +7 -0
- package/dist/integrations/ralph/learning/pattern-learner.js +397 -0
- package/dist/integrations/ralph/learning/pattern-learner.js.map +7 -0
- package/dist/integrations/ralph/lifecycle/iteration-lifecycle.js +444 -0
- package/dist/integrations/ralph/lifecycle/iteration-lifecycle.js.map +7 -0
- package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js +459 -0
- package/dist/integrations/ralph/orchestration/multi-loop-orchestrator.js.map +7 -0
- package/dist/integrations/ralph/performance/performance-optimizer.js +354 -0
- package/dist/integrations/ralph/performance/performance-optimizer.js.map +7 -0
- package/dist/integrations/ralph/ralph-integration-demo.js +178 -0
- package/dist/integrations/ralph/ralph-integration-demo.js.map +7 -0
- package/dist/integrations/ralph/state/state-reconciler.js +400 -0
- package/dist/integrations/ralph/state/state-reconciler.js.map +7 -0
- package/dist/integrations/ralph/swarm/swarm-coordinator.js +487 -0
- package/dist/integrations/ralph/swarm/swarm-coordinator.js.map +7 -0
- package/dist/integrations/ralph/types.js +1 -0
- package/dist/integrations/ralph/types.js.map +7 -0
- package/dist/integrations/ralph/visualization/ralph-debugger.js +581 -0
- package/dist/integrations/ralph/visualization/ralph-debugger.js.map +7 -0
- package/dist/servers/railway/index.js +98 -92
- package/dist/servers/railway/index.js.map +3 -3
- package/package.json +1 -2
- package/scripts/claude-sm-autostart.js +1 -1
- package/scripts/clean-linear-backlog.js +2 -2
- package/scripts/debug-linear-update.js +1 -1
- package/scripts/debug-railway-build.js +87 -0
- package/scripts/delete-linear-tasks.js +2 -2
- package/scripts/deploy-ralph-swarm.sh +365 -0
- package/scripts/install-code-execution-hooks.sh +96 -0
- package/scripts/linear-task-review.js +1 -1
- package/scripts/ralph-integration-test.js +274 -0
- package/scripts/ralph-loop-implementation.js +404 -0
- package/scripts/swarm-monitor.js +509 -0
- package/scripts/sync-and-clean-tasks.js +1 -1
- package/scripts/sync-linear-graphql.js +3 -3
- package/scripts/sync-linear-tasks.js +1 -1
- package/scripts/test-code-execution.js +143 -0
- package/scripts/test-parallel-swarms.js +443 -0
- package/scripts/testing/ralph-cli-test.js +88 -0
- package/scripts/testing/ralph-integration-validation.js +727 -0
- package/scripts/testing/ralph-swarm-test-scenarios.js +613 -0
- package/scripts/update-linear-tasks-fixed.js +1 -1
- package/scripts/validate-railway-deployment.js +137 -0
- package/templates/claude-hooks/hook-config.json +59 -0
- package/templates/claude-hooks/pre-tool-use +189 -0
- package/dist/servers/railway/minimal.js +0 -91
- package/dist/servers/railway/minimal.js.map +0 -7
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Real-time Swarm Monitoring Utility
|
|
5
|
+
* Provides live monitoring and metrics for running swarms
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs/promises';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
import { WebSocketServer } from 'ws';
|
|
12
|
+
import blessed from 'blessed';
|
|
13
|
+
|
|
14
|
+
class SwarmMonitor {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.swarmDir = '.swarm';
|
|
17
|
+
this.statusDir = path.join(this.swarmDir, 'status');
|
|
18
|
+
this.logsDir = path.join(this.swarmDir, 'logs');
|
|
19
|
+
this.metricsHistory = [];
|
|
20
|
+
this.maxHistorySize = 100;
|
|
21
|
+
this.updateInterval = 2000; // 2 seconds
|
|
22
|
+
this.wsPort = 3456;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async startMonitoring() {
|
|
26
|
+
console.log('๐ Starting Swarm Monitor...');
|
|
27
|
+
|
|
28
|
+
// Check if we should use terminal UI or web interface
|
|
29
|
+
const mode = process.argv[2] || 'terminal';
|
|
30
|
+
|
|
31
|
+
if (mode === 'web') {
|
|
32
|
+
await this.startWebMonitor();
|
|
33
|
+
} else {
|
|
34
|
+
await this.startTerminalMonitor();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async startTerminalMonitor() {
|
|
39
|
+
// Create blessed screen
|
|
40
|
+
const screen = blessed.screen({
|
|
41
|
+
smartCSR: true,
|
|
42
|
+
title: 'Ralph Swarm Monitor'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Create layout boxes
|
|
46
|
+
const header = blessed.box({
|
|
47
|
+
parent: screen,
|
|
48
|
+
top: 0,
|
|
49
|
+
left: 0,
|
|
50
|
+
width: '100%',
|
|
51
|
+
height: 3,
|
|
52
|
+
content: ' RALPH SWARM MONITOR ',
|
|
53
|
+
align: 'center',
|
|
54
|
+
style: {
|
|
55
|
+
fg: 'white',
|
|
56
|
+
bg: 'blue',
|
|
57
|
+
bold: true
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const swarmList = blessed.list({
|
|
62
|
+
parent: screen,
|
|
63
|
+
top: 3,
|
|
64
|
+
left: 0,
|
|
65
|
+
width: '50%',
|
|
66
|
+
height: '40%',
|
|
67
|
+
label: ' Active Swarms ',
|
|
68
|
+
border: {
|
|
69
|
+
type: 'line'
|
|
70
|
+
},
|
|
71
|
+
style: {
|
|
72
|
+
fg: 'white',
|
|
73
|
+
border: {
|
|
74
|
+
fg: 'cyan'
|
|
75
|
+
},
|
|
76
|
+
selected: {
|
|
77
|
+
bg: 'blue'
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
mouse: true,
|
|
81
|
+
keys: true,
|
|
82
|
+
vi: true
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const metricsBox = blessed.box({
|
|
86
|
+
parent: screen,
|
|
87
|
+
top: 3,
|
|
88
|
+
left: '50%',
|
|
89
|
+
width: '50%',
|
|
90
|
+
height: '40%',
|
|
91
|
+
label: ' Performance Metrics ',
|
|
92
|
+
border: {
|
|
93
|
+
type: 'line'
|
|
94
|
+
},
|
|
95
|
+
style: {
|
|
96
|
+
fg: 'white',
|
|
97
|
+
border: {
|
|
98
|
+
fg: 'yellow'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const logBox = blessed.log({
|
|
104
|
+
parent: screen,
|
|
105
|
+
bottom: 3,
|
|
106
|
+
left: 0,
|
|
107
|
+
width: '100%',
|
|
108
|
+
height: '55%',
|
|
109
|
+
label: ' Live Logs ',
|
|
110
|
+
border: {
|
|
111
|
+
type: 'line'
|
|
112
|
+
},
|
|
113
|
+
style: {
|
|
114
|
+
fg: 'white',
|
|
115
|
+
border: {
|
|
116
|
+
fg: 'green'
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
scrollable: true,
|
|
120
|
+
alwaysScroll: true,
|
|
121
|
+
mouse: true
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const statusBar = blessed.box({
|
|
125
|
+
parent: screen,
|
|
126
|
+
bottom: 0,
|
|
127
|
+
left: 0,
|
|
128
|
+
width: '100%',
|
|
129
|
+
height: 3,
|
|
130
|
+
style: {
|
|
131
|
+
fg: 'white',
|
|
132
|
+
bg: 'black'
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Handle exit
|
|
137
|
+
screen.key(['q', 'C-c'], () => {
|
|
138
|
+
return process.exit(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Update function
|
|
142
|
+
const updateDisplay = async () => {
|
|
143
|
+
try {
|
|
144
|
+
// Get swarm status
|
|
145
|
+
const swarms = await this.getActiveSwarms();
|
|
146
|
+
|
|
147
|
+
// Update swarm list
|
|
148
|
+
const swarmItems = swarms.map(s => {
|
|
149
|
+
const status = s.status === 'running' ? '๐ข' : '๐ด';
|
|
150
|
+
return `${status} ${s.id} - ${s.project}`;
|
|
151
|
+
});
|
|
152
|
+
swarmList.setItems(swarmItems);
|
|
153
|
+
|
|
154
|
+
// Update metrics
|
|
155
|
+
const metrics = await this.collectMetrics(swarms);
|
|
156
|
+
const metricsContent = this.formatMetrics(metrics);
|
|
157
|
+
metricsBox.setContent(metricsContent);
|
|
158
|
+
|
|
159
|
+
// Update status bar
|
|
160
|
+
const now = new Date().toLocaleTimeString();
|
|
161
|
+
statusBar.setContent(
|
|
162
|
+
` Active: ${swarms.filter(s => s.status === 'running').length} | ` +
|
|
163
|
+
`Total: ${swarms.length} | ` +
|
|
164
|
+
`Updated: ${now} | ` +
|
|
165
|
+
`Press 'q' to quit`
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
screen.render();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
logBox.log(`Error: ${error.message}`);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Handle swarm selection
|
|
175
|
+
swarmList.on('select', async (item, index) => {
|
|
176
|
+
const swarms = await this.getActiveSwarms();
|
|
177
|
+
if (swarms[index]) {
|
|
178
|
+
await this.tailSwarmLogs(swarms[index], logBox);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Start update loop
|
|
183
|
+
await updateDisplay();
|
|
184
|
+
setInterval(updateDisplay, this.updateInterval);
|
|
185
|
+
|
|
186
|
+
screen.render();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async startWebMonitor() {
|
|
190
|
+
// Create HTTP server for web interface
|
|
191
|
+
const server = createServer((req, res) => {
|
|
192
|
+
if (req.url === '/') {
|
|
193
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
194
|
+
res.end(this.getWebInterface());
|
|
195
|
+
} else if (req.url === '/api/swarms') {
|
|
196
|
+
this.handleApiRequest(res);
|
|
197
|
+
} else {
|
|
198
|
+
res.writeHead(404);
|
|
199
|
+
res.end('Not Found');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Create WebSocket server for real-time updates
|
|
204
|
+
const wss = new WebSocketServer({ server });
|
|
205
|
+
|
|
206
|
+
wss.on('connection', (ws) => {
|
|
207
|
+
console.log('Client connected to monitor');
|
|
208
|
+
|
|
209
|
+
// Send initial data
|
|
210
|
+
this.sendSwarmUpdate(ws);
|
|
211
|
+
|
|
212
|
+
// Set up periodic updates
|
|
213
|
+
const updateInterval = setInterval(() => {
|
|
214
|
+
this.sendSwarmUpdate(ws);
|
|
215
|
+
}, this.updateInterval);
|
|
216
|
+
|
|
217
|
+
ws.on('close', () => {
|
|
218
|
+
clearInterval(updateInterval);
|
|
219
|
+
console.log('Client disconnected from monitor');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
server.listen(this.wsPort, () => {
|
|
224
|
+
console.log(`๐ก Web monitor running at http://localhost:${this.wsPort}`);
|
|
225
|
+
console.log('Open in browser to view real-time swarm status');
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getActiveSwarms() {
|
|
230
|
+
const swarms = [];
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const files = await fs.readdir(this.statusDir);
|
|
234
|
+
|
|
235
|
+
for (const file of files) {
|
|
236
|
+
if (file.endsWith('.json')) {
|
|
237
|
+
const filePath = path.join(this.statusDir, file);
|
|
238
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
239
|
+
const swarm = JSON.parse(content);
|
|
240
|
+
|
|
241
|
+
// Check if process is still running
|
|
242
|
+
if (swarm.pid) {
|
|
243
|
+
try {
|
|
244
|
+
process.kill(swarm.pid, 0);
|
|
245
|
+
swarm.status = 'running';
|
|
246
|
+
} catch {
|
|
247
|
+
swarm.status = 'stopped';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
swarms.push(swarm);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
// Directory might not exist
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return swarms;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async collectMetrics(swarms) {
|
|
262
|
+
const metrics = {
|
|
263
|
+
timestamp: Date.now(),
|
|
264
|
+
activeSwarms: swarms.filter(s => s.status === 'running').length,
|
|
265
|
+
totalSwarms: swarms.length,
|
|
266
|
+
agentCount: 0,
|
|
267
|
+
taskCompletion: 0,
|
|
268
|
+
memoryUsage: process.memoryUsage(),
|
|
269
|
+
cpuUsage: process.cpuUsage()
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Count total agents
|
|
273
|
+
for (const swarm of swarms) {
|
|
274
|
+
if (swarm.agents) {
|
|
275
|
+
metrics.agentCount += swarm.agents.split(',').length;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Calculate average task completion (mock for now)
|
|
280
|
+
metrics.taskCompletion = Math.round(Math.random() * 100);
|
|
281
|
+
|
|
282
|
+
// Store in history
|
|
283
|
+
this.metricsHistory.push(metrics);
|
|
284
|
+
if (this.metricsHistory.length > this.maxHistorySize) {
|
|
285
|
+
this.metricsHistory.shift();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return metrics;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
formatMetrics(metrics) {
|
|
292
|
+
const memoryMB = (metrics.memoryUsage.heapUsed / 1024 / 1024).toFixed(2);
|
|
293
|
+
|
|
294
|
+
return `
|
|
295
|
+
Active Swarms: ${metrics.activeSwarms}/${metrics.totalSwarms}
|
|
296
|
+
Total Agents: ${metrics.agentCount}
|
|
297
|
+
Task Completion: ${metrics.taskCompletion}%
|
|
298
|
+
|
|
299
|
+
Memory Usage: ${memoryMB} MB
|
|
300
|
+
CPU Time: ${metrics.cpuUsage.user / 1000}ms
|
|
301
|
+
|
|
302
|
+
Performance Trend:
|
|
303
|
+
${this.getPerformanceTrend()}
|
|
304
|
+
`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getPerformanceTrend() {
|
|
308
|
+
if (this.metricsHistory.length < 2) return 'Collecting data...';
|
|
309
|
+
|
|
310
|
+
const recent = this.metricsHistory.slice(-10);
|
|
311
|
+
let trend = '';
|
|
312
|
+
|
|
313
|
+
for (let i = 0; i < recent.length; i++) {
|
|
314
|
+
const value = recent[i].taskCompletion;
|
|
315
|
+
if (value > 80) trend += 'โ';
|
|
316
|
+
else if (value > 60) trend += 'โ';
|
|
317
|
+
else if (value > 40) trend += 'โ';
|
|
318
|
+
else if (value > 20) trend += 'โ';
|
|
319
|
+
else trend += ' ';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return trend;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async tailSwarmLogs(swarm, logBox) {
|
|
326
|
+
const logFile = swarm.logFile || path.join(this.logsDir, `${swarm.id}.log`);
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const content = await fs.readFile(logFile, 'utf-8');
|
|
330
|
+
const lines = content.split('\n');
|
|
331
|
+
const recentLines = lines.slice(-20);
|
|
332
|
+
|
|
333
|
+
logBox.log(`\n=== Logs for ${swarm.id} ===`);
|
|
334
|
+
recentLines.forEach(line => {
|
|
335
|
+
if (line.trim()) {
|
|
336
|
+
logBox.log(line);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
logBox.log(`Could not read logs for ${swarm.id}: ${error.message}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async sendSwarmUpdate(ws) {
|
|
345
|
+
try {
|
|
346
|
+
const swarms = await this.getActiveSwarms();
|
|
347
|
+
const metrics = await this.collectMetrics(swarms);
|
|
348
|
+
|
|
349
|
+
ws.send(JSON.stringify({
|
|
350
|
+
type: 'update',
|
|
351
|
+
swarms,
|
|
352
|
+
metrics,
|
|
353
|
+
history: this.metricsHistory.slice(-20)
|
|
354
|
+
}));
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error('Failed to send update:', error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async handleApiRequest(res) {
|
|
361
|
+
try {
|
|
362
|
+
const swarms = await this.getActiveSwarms();
|
|
363
|
+
const metrics = await this.collectMetrics(swarms);
|
|
364
|
+
|
|
365
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
366
|
+
res.end(JSON.stringify({ swarms, metrics }));
|
|
367
|
+
} catch (error) {
|
|
368
|
+
res.writeHead(500);
|
|
369
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
getWebInterface() {
|
|
374
|
+
return `
|
|
375
|
+
<!DOCTYPE html>
|
|
376
|
+
<html>
|
|
377
|
+
<head>
|
|
378
|
+
<title>Ralph Swarm Monitor</title>
|
|
379
|
+
<style>
|
|
380
|
+
body {
|
|
381
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
382
|
+
background: #1e1e1e;
|
|
383
|
+
color: #d4d4d4;
|
|
384
|
+
margin: 0;
|
|
385
|
+
padding: 20px;
|
|
386
|
+
}
|
|
387
|
+
h1 {
|
|
388
|
+
color: #569cd6;
|
|
389
|
+
text-align: center;
|
|
390
|
+
}
|
|
391
|
+
.container {
|
|
392
|
+
display: grid;
|
|
393
|
+
grid-template-columns: 1fr 1fr;
|
|
394
|
+
gap: 20px;
|
|
395
|
+
margin-top: 20px;
|
|
396
|
+
}
|
|
397
|
+
.panel {
|
|
398
|
+
background: #2d2d30;
|
|
399
|
+
border: 1px solid #3e3e42;
|
|
400
|
+
border-radius: 5px;
|
|
401
|
+
padding: 15px;
|
|
402
|
+
}
|
|
403
|
+
.panel h2 {
|
|
404
|
+
color: #4ec9b0;
|
|
405
|
+
margin-top: 0;
|
|
406
|
+
}
|
|
407
|
+
.swarm-item {
|
|
408
|
+
padding: 8px;
|
|
409
|
+
margin: 5px 0;
|
|
410
|
+
background: #1e1e1e;
|
|
411
|
+
border-radius: 3px;
|
|
412
|
+
}
|
|
413
|
+
.status-running {
|
|
414
|
+
border-left: 3px solid #4ec9b0;
|
|
415
|
+
}
|
|
416
|
+
.status-stopped {
|
|
417
|
+
border-left: 3px solid #f44747;
|
|
418
|
+
}
|
|
419
|
+
.metrics {
|
|
420
|
+
font-size: 14px;
|
|
421
|
+
}
|
|
422
|
+
.metric-label {
|
|
423
|
+
color: #9cdcfe;
|
|
424
|
+
}
|
|
425
|
+
.metric-value {
|
|
426
|
+
color: #d4d4d4;
|
|
427
|
+
font-weight: bold;
|
|
428
|
+
}
|
|
429
|
+
#logs {
|
|
430
|
+
background: #1e1e1e;
|
|
431
|
+
padding: 10px;
|
|
432
|
+
height: 200px;
|
|
433
|
+
overflow-y: auto;
|
|
434
|
+
font-size: 12px;
|
|
435
|
+
white-space: pre-wrap;
|
|
436
|
+
}
|
|
437
|
+
</style>
|
|
438
|
+
</head>
|
|
439
|
+
<body>
|
|
440
|
+
<h1>๐ Ralph Swarm Monitor</h1>
|
|
441
|
+
|
|
442
|
+
<div class="container">
|
|
443
|
+
<div class="panel">
|
|
444
|
+
<h2>Active Swarms</h2>
|
|
445
|
+
<div id="swarms"></div>
|
|
446
|
+
</div>
|
|
447
|
+
|
|
448
|
+
<div class="panel">
|
|
449
|
+
<h2>Performance Metrics</h2>
|
|
450
|
+
<div id="metrics" class="metrics"></div>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="panel" style="margin-top: 20px;">
|
|
455
|
+
<h2>Live Logs</h2>
|
|
456
|
+
<div id="logs"></div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<script>
|
|
460
|
+
const ws = new WebSocket('ws://localhost:${this.wsPort}');
|
|
461
|
+
|
|
462
|
+
ws.onmessage = (event) => {
|
|
463
|
+
const data = JSON.parse(event.data);
|
|
464
|
+
updateDisplay(data);
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
function updateDisplay(data) {
|
|
468
|
+
// Update swarms
|
|
469
|
+
const swarmsDiv = document.getElementById('swarms');
|
|
470
|
+
swarmsDiv.innerHTML = data.swarms.map(s => \`
|
|
471
|
+
<div class="swarm-item status-\${s.status}">
|
|
472
|
+
<strong>\${s.id}</strong><br>
|
|
473
|
+
Project: \${s.project}<br>
|
|
474
|
+
Agents: \${s.agents}<br>
|
|
475
|
+
Status: \${s.status}
|
|
476
|
+
</div>
|
|
477
|
+
\`).join('');
|
|
478
|
+
|
|
479
|
+
// Update metrics
|
|
480
|
+
const metricsDiv = document.getElementById('metrics');
|
|
481
|
+
metricsDiv.innerHTML = \`
|
|
482
|
+
<p><span class="metric-label">Active Swarms:</span> <span class="metric-value">\${data.metrics.activeSwarms}/\${data.metrics.totalSwarms}</span></p>
|
|
483
|
+
<p><span class="metric-label">Total Agents:</span> <span class="metric-value">\${data.metrics.agentCount}</span></p>
|
|
484
|
+
<p><span class="metric-label">Task Completion:</span> <span class="metric-value">\${data.metrics.taskCompletion}%</span></p>
|
|
485
|
+
<p><span class="metric-label">Memory Usage:</span> <span class="metric-value">\${(data.metrics.memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB</span></p>
|
|
486
|
+
\`;
|
|
487
|
+
|
|
488
|
+
// Update logs (mock for now)
|
|
489
|
+
const logsDiv = document.getElementById('logs');
|
|
490
|
+
if (Math.random() > 0.7) {
|
|
491
|
+
const logEntry = new Date().toLocaleTimeString() + ' - Swarm activity detected\\n';
|
|
492
|
+
logsDiv.textContent += logEntry;
|
|
493
|
+
logsDiv.scrollTop = logsDiv.scrollHeight;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
</script>
|
|
497
|
+
</body>
|
|
498
|
+
</html>
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Run monitor if executed directly
|
|
504
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
505
|
+
const monitor = new SwarmMonitor();
|
|
506
|
+
monitor.startMonitoring().catch(console.error);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export { SwarmMonitor };
|
|
@@ -10,7 +10,7 @@ import path from 'path';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const API_KEY = process.env.LINEAR_OAUTH_TOKEN || process.env.LINEAR_API_KEY;
|
|
13
|
+
const API_KEY = process.env.LINEAR_OAUTH_TOKEN || process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY;
|
|
14
14
|
if (!API_KEY) {
|
|
15
15
|
console.error('โ LINEAR_OAUTH_TOKEN or LINEAR_API_KEY environment variable not set');
|
|
16
16
|
console.log('Please set LINEAR_OAUTH_TOKEN or LINEAR_API_KEY in your .env file or export it in your shell');
|
|
@@ -16,14 +16,14 @@ dotenv.config({
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
// Debug: Check if key is loaded
|
|
19
|
-
console.log(`API Key loaded: ${process.env.LINEAR_API_KEY ? 'Yes' : 'No'} (length: ${process.env.LINEAR_API_KEY?.length || 0})`);
|
|
19
|
+
console.log(`API Key loaded: ${(process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY) ? 'Yes' : 'No'} (length: ${(process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY)?.length || 0})`);
|
|
20
20
|
|
|
21
21
|
async function queryLinear(query, variables = {}) {
|
|
22
22
|
const response = await fetch('https://api.linear.app/graphql', {
|
|
23
23
|
method: 'POST',
|
|
24
24
|
headers: {
|
|
25
25
|
'Content-Type': 'application/json',
|
|
26
|
-
'Authorization': process.env.LINEAR_API_KEY
|
|
26
|
+
'Authorization': process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY
|
|
27
27
|
},
|
|
28
28
|
body: JSON.stringify({ query, variables })
|
|
29
29
|
});
|
|
@@ -36,7 +36,7 @@ async function queryLinear(query, variables = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
async function syncLinearTasks() {
|
|
39
|
-
const apiKey = process.env.LINEAR_API_KEY;
|
|
39
|
+
const apiKey = process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY;
|
|
40
40
|
|
|
41
41
|
if (!apiKey) {
|
|
42
42
|
console.error('โ LINEAR_API_KEY not found in environment');
|
|
@@ -12,7 +12,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
12
12
|
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
13
13
|
|
|
14
14
|
async function syncLinearTasks() {
|
|
15
|
-
const apiKey = process.env.LINEAR_API_KEY;
|
|
15
|
+
const apiKey = process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY;
|
|
16
16
|
|
|
17
17
|
if (!apiKey) {
|
|
18
18
|
console.error('โ LINEAR_API_KEY not found in environment');
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test code execution MCP handler
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CodeExecutionHandler } from '../dist/integrations/mcp/handlers/code-execution-handlers.js';
|
|
8
|
+
|
|
9
|
+
async function testPythonExecution() {
|
|
10
|
+
console.log('๐ Testing Python Code Execution\n');
|
|
11
|
+
|
|
12
|
+
const handler = new CodeExecutionHandler();
|
|
13
|
+
|
|
14
|
+
// Test 1: Simple Python code
|
|
15
|
+
console.log('Test 1: Simple calculation');
|
|
16
|
+
const result1 = await handler.executeCode({
|
|
17
|
+
language: 'python',
|
|
18
|
+
code: `
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
def calculate_circle_area(radius):
|
|
22
|
+
return math.pi * radius ** 2
|
|
23
|
+
|
|
24
|
+
# Test the function
|
|
25
|
+
radius = 5
|
|
26
|
+
area = calculate_circle_area(radius)
|
|
27
|
+
print(f"Circle with radius {radius} has area: {area:.2f}")
|
|
28
|
+
|
|
29
|
+
# Generate some data
|
|
30
|
+
data = [i**2 for i in range(10)]
|
|
31
|
+
print(f"Squares: {data}")
|
|
32
|
+
`,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.log('Success:', result1.success);
|
|
36
|
+
console.log('Output:', result1.stdout);
|
|
37
|
+
if (result1.stderr) console.log('Errors:', result1.stderr);
|
|
38
|
+
console.log('---\n');
|
|
39
|
+
|
|
40
|
+
// Test 2: Code with warnings
|
|
41
|
+
console.log('Test 2: Code with security warnings');
|
|
42
|
+
const code2 = `
|
|
43
|
+
import os
|
|
44
|
+
import subprocess
|
|
45
|
+
|
|
46
|
+
# This should trigger warnings
|
|
47
|
+
print("Current directory:", os.getcwd())
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
const validation = handler.validateCode(code2);
|
|
51
|
+
console.log('Validation result:', validation);
|
|
52
|
+
|
|
53
|
+
if (!validation.safe) {
|
|
54
|
+
console.log('Executing anyway with force flag...');
|
|
55
|
+
const result2 = await handler.executeCode({
|
|
56
|
+
language: 'python',
|
|
57
|
+
code: code2,
|
|
58
|
+
});
|
|
59
|
+
console.log('Output:', result2.stdout);
|
|
60
|
+
}
|
|
61
|
+
console.log('---\n');
|
|
62
|
+
|
|
63
|
+
// Test 3: JavaScript execution
|
|
64
|
+
console.log('Test 3: JavaScript code');
|
|
65
|
+
const result3 = await handler.executeCode({
|
|
66
|
+
language: 'javascript',
|
|
67
|
+
code: `
|
|
68
|
+
// Calculate fibonacci
|
|
69
|
+
function fibonacci(n) {
|
|
70
|
+
if (n <= 1) return n;
|
|
71
|
+
return fibonacci(n - 1) + fibonacci(n - 2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < 10; i++) {
|
|
75
|
+
console.log(\`fibonacci(\${i}) = \${fibonacci(i)}\`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Test async operation
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
console.log('Async operation completed');
|
|
81
|
+
}, 100);
|
|
82
|
+
|
|
83
|
+
// Wait a bit for async
|
|
84
|
+
new Promise(resolve => setTimeout(resolve, 200)).then(() => {
|
|
85
|
+
console.log('Promise resolved');
|
|
86
|
+
});
|
|
87
|
+
`,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log('Success:', result3.success);
|
|
91
|
+
console.log('Output:', result3.stdout);
|
|
92
|
+
console.log('---\n');
|
|
93
|
+
|
|
94
|
+
// Test 4: Long-running code with timeout
|
|
95
|
+
console.log('Test 4: Timeout test');
|
|
96
|
+
const result4 = await handler.executeCode({
|
|
97
|
+
language: 'python',
|
|
98
|
+
code: `
|
|
99
|
+
import time
|
|
100
|
+
print("Starting long operation...")
|
|
101
|
+
time.sleep(5) # This should timeout if timeout is < 5 seconds
|
|
102
|
+
print("This should not print")
|
|
103
|
+
`,
|
|
104
|
+
timeout: 2000, // 2 second timeout
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.log('Success:', result4.success);
|
|
108
|
+
console.log('Output:', result4.stdout);
|
|
109
|
+
console.log('Errors:', result4.stderr);
|
|
110
|
+
console.log('---\n');
|
|
111
|
+
|
|
112
|
+
// Test 5: Large output truncation
|
|
113
|
+
console.log('Test 5: Large output handling');
|
|
114
|
+
const result5 = await handler.executeCode({
|
|
115
|
+
language: 'python',
|
|
116
|
+
code: `
|
|
117
|
+
# Generate large output
|
|
118
|
+
for i in range(10000):
|
|
119
|
+
print(f"Line {i}: {'=' * 50}")
|
|
120
|
+
`,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
console.log('Success:', result5.success);
|
|
124
|
+
console.log('Truncated:', result5.truncated);
|
|
125
|
+
if (result5.truncated) {
|
|
126
|
+
console.log('Output file:', result5.outputFile);
|
|
127
|
+
}
|
|
128
|
+
console.log('Output length:', result5.stdout.length);
|
|
129
|
+
console.log('---\n');
|
|
130
|
+
|
|
131
|
+
// Get sandbox status
|
|
132
|
+
const status = await handler.getSandboxStatus();
|
|
133
|
+
console.log('๐ Sandbox Status:', status);
|
|
134
|
+
|
|
135
|
+
// Clean sandbox
|
|
136
|
+
console.log('\n๐งน Cleaning sandbox...');
|
|
137
|
+
await handler.cleanSandbox();
|
|
138
|
+
const statusAfter = await handler.getSandboxStatus();
|
|
139
|
+
console.log('Sandbox after cleanup:', statusAfter);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Run tests
|
|
143
|
+
testPythonExecution().catch(console.error);
|