@rajat-rastogi/maestro 0.5.6 → 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 +118 -26
- 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 {
|
|
@@ -1389,13 +1420,8 @@ class AgentPtyAdapter {
|
|
|
1389
1420
|
};
|
|
1390
1421
|
this.ws.on("message", this.messageHandler);
|
|
1391
1422
|
this.ws.on("close", () => {
|
|
1392
|
-
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket closed
|
|
1393
|
-
|
|
1394
|
-
this.dataCallback("\r\n\x1B[1;31m[Maestro] Connection to remote agent lost. Close and recreate the terminal.\x1B[0m\r\n");
|
|
1395
|
-
}
|
|
1396
|
-
if (this.exitCallback) {
|
|
1397
|
-
this.exitCallback(null);
|
|
1398
|
-
}
|
|
1423
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket closed`);
|
|
1424
|
+
this.handleConnectionLost();
|
|
1399
1425
|
});
|
|
1400
1426
|
this.ws.on("error", (err) => {
|
|
1401
1427
|
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} WebSocket error: ${err.message}`);
|
|
@@ -1417,6 +1443,61 @@ class AgentPtyAdapter {
|
|
|
1417
1443
|
this.ws.on("message", ackHandler);
|
|
1418
1444
|
});
|
|
1419
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
|
+
}
|
|
1420
1501
|
onData(cb) {
|
|
1421
1502
|
this.dataCallback = cb;
|
|
1422
1503
|
}
|
|
@@ -1429,15 +1510,20 @@ class AgentPtyAdapter {
|
|
|
1429
1510
|
}
|
|
1430
1511
|
}
|
|
1431
1512
|
resize(cols, rows) {
|
|
1513
|
+
this.lastCols = cols;
|
|
1514
|
+
this.lastRows = rows;
|
|
1432
1515
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1433
1516
|
this.ws.send(JSON.stringify({ type: "resize", id: this.sessionId, cols, rows }));
|
|
1434
1517
|
}
|
|
1435
1518
|
}
|
|
1436
1519
|
kill() {
|
|
1520
|
+
this.dead = true;
|
|
1437
1521
|
if (this.ws && this.messageHandler) {
|
|
1438
1522
|
this.ws.removeListener("message", this.messageHandler);
|
|
1439
1523
|
}
|
|
1440
|
-
this.ws?.
|
|
1524
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1525
|
+
this.ws.send(JSON.stringify({ type: "destroy", id: this.sessionId }));
|
|
1526
|
+
}
|
|
1441
1527
|
}
|
|
1442
1528
|
autoLaunch(provider) {
|
|
1443
1529
|
if (provider === "claude") {
|
|
@@ -1884,26 +1970,32 @@ class SessionManager {
|
|
|
1884
1970
|
this.push("sessions:stateChanged", ev);
|
|
1885
1971
|
}
|
|
1886
1972
|
}
|
|
1887
|
-
|
|
1888
|
-
const logFile = path.join(logDir, "maestro-gui.log");
|
|
1973
|
+
let logStream = null;
|
|
1889
1974
|
try {
|
|
1890
|
-
|
|
1891
|
-
|
|
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);
|
|
1892
1998
|
}
|
|
1893
|
-
const logStream = fs__namespace.createWriteStream(logFile, { flags: "a" });
|
|
1894
|
-
const origLog = console.log;
|
|
1895
|
-
const origErr = console.error;
|
|
1896
|
-
console.log = (...args) => {
|
|
1897
|
-
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${args.map(String).join(" ")}`;
|
|
1898
|
-
logStream.write(line + "\n");
|
|
1899
|
-
origLog(...args);
|
|
1900
|
-
};
|
|
1901
|
-
console.error = (...args) => {
|
|
1902
|
-
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ERROR ${args.map(String).join(" ")}`;
|
|
1903
|
-
logStream.write(line + "\n");
|
|
1904
|
-
origErr(...args);
|
|
1905
|
-
};
|
|
1906
|
-
console.log(`[Maestro] Log file: ${logFile}`);
|
|
1907
1999
|
const startupArgs = process.argv.slice(2);
|
|
1908
2000
|
let startupPlanFile = null;
|
|
1909
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}\`);
|