@rajat-rastogi/maestro 0.5.5 → 0.6.0
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/app/out/main/main.js +131 -3
- package/dist/agent/install.js +10 -1
- package/package.json +1 -1
package/app/out/main/main.js
CHANGED
|
@@ -580,6 +580,29 @@ class AgentConnectionPool {
|
|
|
580
580
|
entry.ready = true;
|
|
581
581
|
entry.queue.forEach((cb) => cb(ws));
|
|
582
582
|
entry.queue = [];
|
|
583
|
+
entry.heartbeat = setInterval(() => {
|
|
584
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
585
|
+
let gotPong = false;
|
|
586
|
+
const handler = (data) => {
|
|
587
|
+
try {
|
|
588
|
+
const msg = JSON.parse(data.toString());
|
|
589
|
+
if (msg.type === "pong") {
|
|
590
|
+
gotPong = true;
|
|
591
|
+
ws.removeListener("message", handler);
|
|
592
|
+
}
|
|
593
|
+
} catch {
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
ws.on("message", handler);
|
|
597
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
ws.removeListener("message", handler);
|
|
600
|
+
if (!gotPong && ws.readyState === WebSocket.OPEN) {
|
|
601
|
+
console.log(`[AgentPool] heartbeat failed for "${tunnelId}" — zombie connection, closing`);
|
|
602
|
+
ws.terminate();
|
|
603
|
+
}
|
|
604
|
+
}, 1e4);
|
|
605
|
+
}, 6e4);
|
|
583
606
|
resolve(ws);
|
|
584
607
|
});
|
|
585
608
|
ws.on("error", (err) => {
|
|
@@ -685,6 +708,7 @@ class AgentConnectionPool {
|
|
|
685
708
|
teardown(tunnelId) {
|
|
686
709
|
const entry = this.pool.get(tunnelId);
|
|
687
710
|
if (entry) {
|
|
711
|
+
if (entry.heartbeat) clearInterval(entry.heartbeat);
|
|
688
712
|
try {
|
|
689
713
|
entry.ws?.close();
|
|
690
714
|
} catch {
|
|
@@ -692,6 +716,9 @@ class AgentConnectionPool {
|
|
|
692
716
|
this.pool.delete(tunnelId);
|
|
693
717
|
}
|
|
694
718
|
}
|
|
719
|
+
releaseOne(tunnelId) {
|
|
720
|
+
this.teardown(tunnelId);
|
|
721
|
+
}
|
|
695
722
|
releaseAll() {
|
|
696
723
|
for (const id of Array.from(this.pool.keys())) {
|
|
697
724
|
this.teardown(id);
|
|
@@ -1373,7 +1400,11 @@ class AgentPtyAdapter {
|
|
|
1373
1400
|
dataCallback = null;
|
|
1374
1401
|
exitCallback = null;
|
|
1375
1402
|
messageHandler = null;
|
|
1403
|
+
dead = false;
|
|
1376
1404
|
async connect(cwd, cols, rows) {
|
|
1405
|
+
this.lastCwd = cwd;
|
|
1406
|
+
this.lastCols = cols;
|
|
1407
|
+
this.lastRows = rows;
|
|
1377
1408
|
this.ws = await AgentConnectionPool.getInstance().getConnection(this.tunnelId);
|
|
1378
1409
|
this.messageHandler = (raw) => {
|
|
1379
1410
|
try {
|
|
@@ -1388,6 +1419,13 @@ class AgentPtyAdapter {
|
|
|
1388
1419
|
}
|
|
1389
1420
|
};
|
|
1390
1421
|
this.ws.on("message", this.messageHandler);
|
|
1422
|
+
this.ws.on("close", () => {
|
|
1423
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket closed`);
|
|
1424
|
+
this.handleConnectionLost();
|
|
1425
|
+
});
|
|
1426
|
+
this.ws.on("error", (err) => {
|
|
1427
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket error: ${err.message}`);
|
|
1428
|
+
});
|
|
1391
1429
|
this.ws.send(JSON.stringify({ type: "create", id: this.sessionId, shell: "powershell", cwd, cols, rows }));
|
|
1392
1430
|
await new Promise((resolve) => {
|
|
1393
1431
|
const t = setTimeout(resolve, 5e3);
|
|
@@ -1405,6 +1443,61 @@ class AgentPtyAdapter {
|
|
|
1405
1443
|
this.ws.on("message", ackHandler);
|
|
1406
1444
|
});
|
|
1407
1445
|
}
|
|
1446
|
+
reconnectAttempts = 0;
|
|
1447
|
+
lastCwd = "";
|
|
1448
|
+
lastCols = 120;
|
|
1449
|
+
lastRows = 30;
|
|
1450
|
+
async handleConnectionLost() {
|
|
1451
|
+
if (this.dead) return;
|
|
1452
|
+
if (this.reconnectAttempts < 3) {
|
|
1453
|
+
this.reconnectAttempts++;
|
|
1454
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnecting (attempt ${this.reconnectAttempts}/3)...`);
|
|
1455
|
+
if (this.dataCallback) {
|
|
1456
|
+
this.dataCallback(`\r
|
|
1457
|
+
\x1B[1;33m[Maestro] Connection lost — reconnecting (${this.reconnectAttempts}/3)...\x1B[0m\r
|
|
1458
|
+
`);
|
|
1459
|
+
}
|
|
1460
|
+
AgentConnectionPool.getInstance().releaseOne(this.tunnelId);
|
|
1461
|
+
await new Promise((r) => setTimeout(r, 2e3 * this.reconnectAttempts));
|
|
1462
|
+
try {
|
|
1463
|
+
this.ws = await AgentConnectionPool.getInstance().getConnection(this.tunnelId);
|
|
1464
|
+
if (this.messageHandler) {
|
|
1465
|
+
this.ws.on("message", this.messageHandler);
|
|
1466
|
+
}
|
|
1467
|
+
this.ws.on("close", () => {
|
|
1468
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket closed`);
|
|
1469
|
+
this.handleConnectionLost();
|
|
1470
|
+
});
|
|
1471
|
+
this.ws.on("error", (err) => {
|
|
1472
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket error: ${err.message}`);
|
|
1473
|
+
});
|
|
1474
|
+
this.ws.send(JSON.stringify({
|
|
1475
|
+
type: "create",
|
|
1476
|
+
id: this.sessionId,
|
|
1477
|
+
shell: "powershell",
|
|
1478
|
+
cwd: this.lastCwd,
|
|
1479
|
+
cols: this.lastCols,
|
|
1480
|
+
rows: this.lastRows
|
|
1481
|
+
}));
|
|
1482
|
+
this.reconnectAttempts = 0;
|
|
1483
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnected successfully`);
|
|
1484
|
+
if (this.dataCallback) {
|
|
1485
|
+
this.dataCallback("\r\n\x1B[1;32m[Maestro] Reconnected to remote agent.\x1B[0m\r\n");
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnect failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
this.dead = true;
|
|
1493
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} all reconnect attempts failed`);
|
|
1494
|
+
if (this.dataCallback) {
|
|
1495
|
+
this.dataCallback("\r\n\x1B[1;31m[Maestro] Connection to remote agent lost. Close and recreate the terminal.\x1B[0m\r\n");
|
|
1496
|
+
}
|
|
1497
|
+
if (this.exitCallback) {
|
|
1498
|
+
this.exitCallback(null);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1408
1501
|
onData(cb) {
|
|
1409
1502
|
this.dataCallback = cb;
|
|
1410
1503
|
}
|
|
@@ -1412,16 +1505,25 @@ class AgentPtyAdapter {
|
|
|
1412
1505
|
this.exitCallback = cb;
|
|
1413
1506
|
}
|
|
1414
1507
|
write(data) {
|
|
1415
|
-
this.ws?.
|
|
1508
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1509
|
+
this.ws.send(JSON.stringify({ type: "input", id: this.sessionId, data }));
|
|
1510
|
+
}
|
|
1416
1511
|
}
|
|
1417
1512
|
resize(cols, rows) {
|
|
1418
|
-
this.
|
|
1513
|
+
this.lastCols = cols;
|
|
1514
|
+
this.lastRows = rows;
|
|
1515
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1516
|
+
this.ws.send(JSON.stringify({ type: "resize", id: this.sessionId, cols, rows }));
|
|
1517
|
+
}
|
|
1419
1518
|
}
|
|
1420
1519
|
kill() {
|
|
1520
|
+
this.dead = true;
|
|
1421
1521
|
if (this.ws && this.messageHandler) {
|
|
1422
1522
|
this.ws.removeListener("message", this.messageHandler);
|
|
1423
1523
|
}
|
|
1424
|
-
this.ws?.
|
|
1524
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1525
|
+
this.ws.send(JSON.stringify({ type: "destroy", id: this.sessionId }));
|
|
1526
|
+
}
|
|
1425
1527
|
}
|
|
1426
1528
|
autoLaunch(provider) {
|
|
1427
1529
|
if (provider === "claude") {
|
|
@@ -1868,6 +1970,32 @@ class SessionManager {
|
|
|
1868
1970
|
this.push("sessions:stateChanged", ev);
|
|
1869
1971
|
}
|
|
1870
1972
|
}
|
|
1973
|
+
let logStream = null;
|
|
1974
|
+
try {
|
|
1975
|
+
const logDir = electron.app.getPath("userData");
|
|
1976
|
+
fs__namespace.mkdirSync(logDir, { recursive: true });
|
|
1977
|
+
const logFile = path.join(logDir, "maestro-gui.log");
|
|
1978
|
+
try {
|
|
1979
|
+
if (fs__namespace.existsSync(logFile)) fs__namespace.renameSync(logFile, logFile + ".prev");
|
|
1980
|
+
} catch {
|
|
1981
|
+
}
|
|
1982
|
+
logStream = fs__namespace.createWriteStream(logFile, { flags: "a" });
|
|
1983
|
+
const origLog = console.log;
|
|
1984
|
+
const origErr = console.error;
|
|
1985
|
+
console.log = (...args) => {
|
|
1986
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${args.map(String).join(" ")}`;
|
|
1987
|
+
logStream?.write(line + "\n");
|
|
1988
|
+
origLog(...args);
|
|
1989
|
+
};
|
|
1990
|
+
console.error = (...args) => {
|
|
1991
|
+
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ERROR ${args.map(String).join(" ")}`;
|
|
1992
|
+
logStream?.write(line + "\n");
|
|
1993
|
+
origErr(...args);
|
|
1994
|
+
};
|
|
1995
|
+
console.log(`[Maestro] Log file: ${logFile}`);
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
console.error("[Maestro] Failed to set up log file:", err);
|
|
1998
|
+
}
|
|
1871
1999
|
const startupArgs = process.argv.slice(2);
|
|
1872
2000
|
let startupPlanFile = null;
|
|
1873
2001
|
const pfIdx = startupArgs.indexOf("--plan-file");
|
package/dist/agent/install.js
CHANGED
|
@@ -129,6 +129,15 @@ wss.on('connection', (ws) => {
|
|
|
129
129
|
ws.send(JSON.stringify({ type: 'pong' }));
|
|
130
130
|
break;
|
|
131
131
|
case 'create': {
|
|
132
|
+
// If session already exists (reconnect after connection drop), reattach to new WebSocket
|
|
133
|
+
if (sessions.has(msg.id)) {
|
|
134
|
+
const proc = sessions.get(msg.id);
|
|
135
|
+
proc.removeAllListeners('data');
|
|
136
|
+
proc.onData((data) => ws.send(JSON.stringify({ type: 'data', id: msg.id, data: Buffer.from(data).toString('base64') })));
|
|
137
|
+
ws.send(JSON.stringify({ type: 'created', id: msg.id }));
|
|
138
|
+
console.log('[agent] session reattached:', msg.id);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
132
141
|
const proc = pty.spawn('powershell.exe', [], {
|
|
133
142
|
name: 'xterm-256color', cols: msg.cols, rows: msg.rows, cwd: msg.cwd,
|
|
134
143
|
env: { ...process.env },
|
|
@@ -167,7 +176,7 @@ wss.on('connection', (ws) => {
|
|
|
167
176
|
}
|
|
168
177
|
}
|
|
169
178
|
});
|
|
170
|
-
ws.on('close', () => { console.log('[agent] client disconnected
|
|
179
|
+
ws.on('close', () => { console.log('[agent] client disconnected — sessions kept alive for reconnect'); });
|
|
171
180
|
});
|
|
172
181
|
|
|
173
182
|
console.log(\`[agent] Maestro agent listening on ws://127.0.0.1:\${PORT}\`);
|