@rajat-rastogi/maestro 0.6.0 → 0.7.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/app/out/main/main.js
CHANGED
|
@@ -503,6 +503,15 @@ function registerSessionsIpc(sessionManager2) {
|
|
|
503
503
|
electron.ipcMain.handle("sessions:resize", async (_e, id, cols, rows) => {
|
|
504
504
|
sessionManager2.resize(id, cols, rows);
|
|
505
505
|
});
|
|
506
|
+
electron.ipcMain.handle("sessions:savedList", async () => {
|
|
507
|
+
return sessionManager2.listSaved();
|
|
508
|
+
});
|
|
509
|
+
electron.ipcMain.handle("sessions:savedResume", async (_e, ids, resumeAI) => {
|
|
510
|
+
return sessionManager2.resumeSaved(ids, resumeAI);
|
|
511
|
+
});
|
|
512
|
+
electron.ipcMain.handle("sessions:savedDismiss", async () => {
|
|
513
|
+
sessionManager2.dismissSaved();
|
|
514
|
+
});
|
|
506
515
|
}
|
|
507
516
|
const execAsync$2 = util.promisify(child_process.exec);
|
|
508
517
|
class AgentConnectionPool {
|
|
@@ -580,8 +589,12 @@ class AgentConnectionPool {
|
|
|
580
589
|
entry.ready = true;
|
|
581
590
|
entry.queue.forEach((cb) => cb(ws));
|
|
582
591
|
entry.queue = [];
|
|
592
|
+
console.log(`[AgentPool] heartbeat started for "${tunnelId}" (60s interval)`);
|
|
583
593
|
entry.heartbeat = setInterval(() => {
|
|
584
|
-
if (ws.readyState !== WebSocket.OPEN)
|
|
594
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
595
|
+
console.log(`[AgentPool] heartbeat skip for "${tunnelId}" — ws not open (state: ${ws.readyState})`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
585
598
|
let gotPong = false;
|
|
586
599
|
const handler = (data) => {
|
|
587
600
|
try {
|
|
@@ -1184,11 +1197,20 @@ class LocalPtyAdapter {
|
|
|
1184
1197
|
} catch {
|
|
1185
1198
|
}
|
|
1186
1199
|
}
|
|
1187
|
-
autoLaunch(provider) {
|
|
1200
|
+
autoLaunch(provider, skipPermissions, resumeAI) {
|
|
1188
1201
|
if (provider === "claude") {
|
|
1189
|
-
|
|
1202
|
+
const flags = [
|
|
1203
|
+
resumeAI ? "--continue" : "",
|
|
1204
|
+
skipPermissions ? "--dangerously-skip-permissions" : ""
|
|
1205
|
+
].filter(Boolean).join(" ");
|
|
1206
|
+
setTimeout(() => this.write(`claude${flags ? " " + flags : ""}\r`), 800);
|
|
1190
1207
|
} else if (provider === "copilot") {
|
|
1191
|
-
|
|
1208
|
+
const flags = [
|
|
1209
|
+
"--output-format json",
|
|
1210
|
+
resumeAI ? "--continue" : "",
|
|
1211
|
+
skipPermissions ? "--yolo" : ""
|
|
1212
|
+
].filter(Boolean).join(" ");
|
|
1213
|
+
setTimeout(() => this.write(`copilot ${flags}\r`), 800);
|
|
1192
1214
|
}
|
|
1193
1215
|
}
|
|
1194
1216
|
}
|
|
@@ -1383,11 +1405,20 @@ class SshPtyAdapter {
|
|
|
1383
1405
|
this.sshClient = null;
|
|
1384
1406
|
this.devtunnelProc = null;
|
|
1385
1407
|
}
|
|
1386
|
-
autoLaunch(provider) {
|
|
1408
|
+
autoLaunch(provider, skipPermissions, resumeAI) {
|
|
1387
1409
|
if (provider === "claude") {
|
|
1388
|
-
|
|
1410
|
+
const flags = [
|
|
1411
|
+
resumeAI ? "--continue" : "",
|
|
1412
|
+
skipPermissions ? "--dangerously-skip-permissions" : ""
|
|
1413
|
+
].filter(Boolean).join(" ");
|
|
1414
|
+
setTimeout(() => this.write(`claude${flags ? " " + flags : ""}\r`), 1200);
|
|
1389
1415
|
} else if (provider === "copilot") {
|
|
1390
|
-
|
|
1416
|
+
const flags = [
|
|
1417
|
+
"--output-format json",
|
|
1418
|
+
resumeAI ? "--continue" : "",
|
|
1419
|
+
skipPermissions ? "--yolo" : ""
|
|
1420
|
+
].filter(Boolean).join(" ");
|
|
1421
|
+
setTimeout(() => this.write(`copilot ${flags}\r`), 1200);
|
|
1391
1422
|
}
|
|
1392
1423
|
}
|
|
1393
1424
|
}
|
|
@@ -1449,16 +1480,15 @@ class AgentPtyAdapter {
|
|
|
1449
1480
|
lastRows = 30;
|
|
1450
1481
|
async handleConnectionLost() {
|
|
1451
1482
|
if (this.dead) return;
|
|
1452
|
-
|
|
1453
|
-
this.
|
|
1454
|
-
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnecting (attempt ${this.reconnectAttempts}/3)...`);
|
|
1483
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
1484
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnecting (attempt ${attempt}/3)...`);
|
|
1455
1485
|
if (this.dataCallback) {
|
|
1456
1486
|
this.dataCallback(`\r
|
|
1457
|
-
\x1B[1;33m[Maestro] Connection lost — reconnecting (${
|
|
1487
|
+
\x1B[1;33m[Maestro] Connection lost — reconnecting (${attempt}/3)...\x1B[0m\r
|
|
1458
1488
|
`);
|
|
1459
1489
|
}
|
|
1460
1490
|
AgentConnectionPool.getInstance().releaseOne(this.tunnelId);
|
|
1461
|
-
await new Promise((r) => setTimeout(r, 2e3 *
|
|
1491
|
+
await new Promise((r) => setTimeout(r, 2e3 * attempt));
|
|
1462
1492
|
try {
|
|
1463
1493
|
this.ws = await AgentConnectionPool.getInstance().getConnection(this.tunnelId);
|
|
1464
1494
|
if (this.messageHandler) {
|
|
@@ -1479,14 +1509,13 @@ class AgentPtyAdapter {
|
|
|
1479
1509
|
cols: this.lastCols,
|
|
1480
1510
|
rows: this.lastRows
|
|
1481
1511
|
}));
|
|
1482
|
-
this.reconnectAttempts = 0;
|
|
1483
1512
|
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnected successfully`);
|
|
1484
1513
|
if (this.dataCallback) {
|
|
1485
1514
|
this.dataCallback("\r\n\x1B[1;32m[Maestro] Reconnected to remote agent.\x1B[0m\r\n");
|
|
1486
1515
|
}
|
|
1487
1516
|
return;
|
|
1488
1517
|
} catch (err) {
|
|
1489
|
-
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnect failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1518
|
+
console.log(`[AgentPty] ${this.sessionId.slice(0, 8)} reconnect attempt ${attempt} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1490
1519
|
}
|
|
1491
1520
|
}
|
|
1492
1521
|
this.dead = true;
|
|
@@ -1525,11 +1554,20 @@ class AgentPtyAdapter {
|
|
|
1525
1554
|
this.ws.send(JSON.stringify({ type: "destroy", id: this.sessionId }));
|
|
1526
1555
|
}
|
|
1527
1556
|
}
|
|
1528
|
-
autoLaunch(provider) {
|
|
1557
|
+
autoLaunch(provider, skipPermissions, resumeAI) {
|
|
1529
1558
|
if (provider === "claude") {
|
|
1530
|
-
|
|
1559
|
+
const flags = [
|
|
1560
|
+
resumeAI ? "--continue" : "",
|
|
1561
|
+
skipPermissions ? "--dangerously-skip-permissions" : ""
|
|
1562
|
+
].filter(Boolean).join(" ");
|
|
1563
|
+
setTimeout(() => this.write(`claude${flags ? " " + flags : ""}\r`), 600);
|
|
1531
1564
|
} else if (provider === "copilot") {
|
|
1532
|
-
|
|
1565
|
+
const flags = [
|
|
1566
|
+
"--output-format json",
|
|
1567
|
+
resumeAI ? "--continue" : "",
|
|
1568
|
+
skipPermissions ? "--yolo" : ""
|
|
1569
|
+
].filter(Boolean).join(" ");
|
|
1570
|
+
setTimeout(() => this.write(`copilot ${flags}\r`), 600);
|
|
1533
1571
|
}
|
|
1534
1572
|
}
|
|
1535
1573
|
}
|
|
@@ -1584,7 +1622,7 @@ class StateMonitor {
|
|
|
1584
1622
|
if (this.copilotReady && data.length >= 20) {
|
|
1585
1623
|
if (data.includes("Enter to select")) {
|
|
1586
1624
|
this.emitState("permission", "heuristic", '"Enter to select" — input required');
|
|
1587
|
-
} else if (data.includes("Esc to cancel") || data.includes("Esc to stop")) {
|
|
1625
|
+
} else if ((data.includes("Esc to cancel") || data.includes("Esc to stop")) && !this.lastEmittedState?.startsWith("permission")) {
|
|
1588
1626
|
this.emitState("working", "heuristic", '"Esc to cancel/stop" — copilot working');
|
|
1589
1627
|
} else if (data.includes("for shortcuts") || data.includes("Describe a task") || data.includes("to get started")) {
|
|
1590
1628
|
this.emitState("waiting", "heuristic", "copilot prompt detected — at prompt");
|
|
@@ -1832,9 +1870,65 @@ class SessionManager {
|
|
|
1832
1870
|
entries = /* @__PURE__ */ new Map();
|
|
1833
1871
|
machineManager;
|
|
1834
1872
|
push;
|
|
1873
|
+
savedPath;
|
|
1874
|
+
savedSessions = [];
|
|
1835
1875
|
constructor(machineManager2, push) {
|
|
1836
1876
|
this.machineManager = machineManager2;
|
|
1837
1877
|
this.push = push;
|
|
1878
|
+
this.savedPath = path__namespace.join(electron.app.getPath("userData"), "saved-sessions.json");
|
|
1879
|
+
this.loadSaved();
|
|
1880
|
+
}
|
|
1881
|
+
loadSaved() {
|
|
1882
|
+
try {
|
|
1883
|
+
const data = fs__namespace.readFileSync(this.savedPath, "utf8");
|
|
1884
|
+
this.savedSessions = JSON.parse(data);
|
|
1885
|
+
console.log(`[SessionManager] Loaded ${this.savedSessions.length} saved sessions`);
|
|
1886
|
+
} catch {
|
|
1887
|
+
this.savedSessions = [];
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
saveToDisk() {
|
|
1891
|
+
try {
|
|
1892
|
+
fs__namespace.writeFileSync(this.savedPath, JSON.stringify(this.savedSessions, null, 2), "utf8");
|
|
1893
|
+
} catch (err) {
|
|
1894
|
+
console.error("[SessionManager] Failed to save sessions:", err);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
listSaved() {
|
|
1898
|
+
return this.savedSessions;
|
|
1899
|
+
}
|
|
1900
|
+
dismissSaved() {
|
|
1901
|
+
this.savedSessions = [];
|
|
1902
|
+
this.saveToDisk();
|
|
1903
|
+
}
|
|
1904
|
+
async resumeSaved(ids, resumeAI) {
|
|
1905
|
+
const resumed = [];
|
|
1906
|
+
const skipped = [];
|
|
1907
|
+
for (const saved of this.savedSessions.filter((s) => ids.includes(s.id))) {
|
|
1908
|
+
const machine = this.machineManager.getById(saved.machineId);
|
|
1909
|
+
if (saved.machineId !== "local" && (!machine || machine.status === "offline")) {
|
|
1910
|
+
skipped.push(saved.name);
|
|
1911
|
+
continue;
|
|
1912
|
+
}
|
|
1913
|
+
try {
|
|
1914
|
+
const session = await this.create({
|
|
1915
|
+
name: saved.name,
|
|
1916
|
+
machineId: saved.machineId,
|
|
1917
|
+
provider: saved.provider,
|
|
1918
|
+
workingDirectory: saved.workingDirectory,
|
|
1919
|
+
tags: saved.tags,
|
|
1920
|
+
skipPermissions: saved.skipPermissions,
|
|
1921
|
+
resumeAI: resumeAI && saved.provider !== "none"
|
|
1922
|
+
});
|
|
1923
|
+
resumed.push(session);
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
console.error(`[SessionManager] Failed to resume "${saved.name}":`, err);
|
|
1926
|
+
skipped.push(saved.name);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
this.savedSessions = [];
|
|
1930
|
+
this.saveToDisk();
|
|
1931
|
+
return { resumed, skipped };
|
|
1838
1932
|
}
|
|
1839
1933
|
list() {
|
|
1840
1934
|
return Array.from(this.entries.values()).map((e) => ({
|
|
@@ -1913,8 +2007,20 @@ class SessionManager {
|
|
|
1913
2007
|
});
|
|
1914
2008
|
monitor.start();
|
|
1915
2009
|
if (opts.provider !== "none") {
|
|
1916
|
-
adapter.autoLaunch(opts.provider);
|
|
1917
|
-
}
|
|
2010
|
+
adapter.autoLaunch(opts.provider, opts.skipPermissions, opts.resumeAI);
|
|
2011
|
+
}
|
|
2012
|
+
this.savedSessions = this.savedSessions.filter((s) => s.id !== session.id);
|
|
2013
|
+
this.savedSessions.push({
|
|
2014
|
+
id: session.id,
|
|
2015
|
+
name: session.name,
|
|
2016
|
+
machineId: session.machineId,
|
|
2017
|
+
provider: session.provider,
|
|
2018
|
+
workingDirectory: session.workingDirectory,
|
|
2019
|
+
tags: session.tags,
|
|
2020
|
+
skipPermissions: opts.skipPermissions,
|
|
2021
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2022
|
+
});
|
|
2023
|
+
this.saveToDisk();
|
|
1918
2024
|
return { ...session };
|
|
1919
2025
|
}
|
|
1920
2026
|
destroy(id) {
|
|
@@ -1923,6 +2029,8 @@ class SessionManager {
|
|
|
1923
2029
|
entry.monitor.stop();
|
|
1924
2030
|
entry.adapter.kill();
|
|
1925
2031
|
this.entries.delete(id);
|
|
2032
|
+
this.savedSessions = this.savedSessions.filter((s) => s.id !== id);
|
|
2033
|
+
this.saveToDisk();
|
|
1926
2034
|
}
|
|
1927
2035
|
rename(id, name) {
|
|
1928
2036
|
const entry = this.entries.get(id);
|
|
@@ -2069,6 +2177,15 @@ electron.app.whenReady().then(() => {
|
|
|
2069
2177
|
electron.ipcMain.handle("app:getStartupArgs", () => ({
|
|
2070
2178
|
planFile: startupPlanFile
|
|
2071
2179
|
}));
|
|
2180
|
+
electron.ipcMain.handle("app:getVersion", () => {
|
|
2181
|
+
try {
|
|
2182
|
+
const pkgPath = path.join(__dirname, "..", "..", "..", "package.json");
|
|
2183
|
+
const pkg = JSON.parse(fs__namespace.readFileSync(pkgPath, "utf8"));
|
|
2184
|
+
return pkg.version ?? "unknown";
|
|
2185
|
+
} catch {
|
|
2186
|
+
return electron.app.getVersion();
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2072
2189
|
createWindow();
|
|
2073
2190
|
electron.app.on("activate", () => {
|
|
2074
2191
|
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
|
@@ -55,6 +55,7 @@ electron.contextBridge.exposeInMainWorld("maestro", {
|
|
|
55
55
|
},
|
|
56
56
|
// Read startup args passed from CLI
|
|
57
57
|
getStartupArgs: () => electron.ipcRenderer.invoke("app:getStartupArgs"),
|
|
58
|
+
getVersion: () => electron.ipcRenderer.invoke("app:getVersion"),
|
|
58
59
|
// Machine management
|
|
59
60
|
machines: {
|
|
60
61
|
list: () => electron.ipcRenderer.invoke("machines:list"),
|
|
@@ -84,6 +85,9 @@ electron.contextBridge.exposeInMainWorld("maestro", {
|
|
|
84
85
|
acknowledge: (id) => electron.ipcRenderer.invoke("sessions:acknowledge", id),
|
|
85
86
|
sendInput: (id, data) => electron.ipcRenderer.invoke("sessions:sendInput", id, data),
|
|
86
87
|
resize: (id, cols, rows) => electron.ipcRenderer.invoke("sessions:resize", id, cols, rows),
|
|
88
|
+
savedList: () => electron.ipcRenderer.invoke("sessions:savedList"),
|
|
89
|
+
savedResume: (ids, resumeAI) => electron.ipcRenderer.invoke("sessions:savedResume", ids, resumeAI),
|
|
90
|
+
savedDismiss: () => electron.ipcRenderer.invoke("sessions:savedDismiss"),
|
|
87
91
|
onData: (cb) => {
|
|
88
92
|
const handler = (_, ev) => cb(ev);
|
|
89
93
|
electron.ipcRenderer.on("sessions:data", handler);
|