@rajat-rastogi/maestro 0.2.5 → 0.3.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
CHANGED
|
@@ -488,6 +488,9 @@ function registerSessionsIpc(sessionManager2) {
|
|
|
488
488
|
electron.ipcMain.handle("sessions:updateTags", async (_e, id, tags) => {
|
|
489
489
|
sessionManager2.updateTags(id, tags);
|
|
490
490
|
});
|
|
491
|
+
electron.ipcMain.handle("sessions:interrupt", async (_e, id) => {
|
|
492
|
+
sessionManager2.interrupt(id);
|
|
493
|
+
});
|
|
491
494
|
electron.ipcMain.handle("sessions:sendInput", async (_e, id, data) => {
|
|
492
495
|
sessionManager2.sendInput(id, data);
|
|
493
496
|
});
|
|
@@ -507,56 +510,77 @@ class AgentConnectionPool {
|
|
|
507
510
|
if (local) env.PATH = `${local}\\Microsoft\\WinGet\\Links;${env.PATH ?? ""}`;
|
|
508
511
|
return env;
|
|
509
512
|
}
|
|
510
|
-
/**
|
|
513
|
+
/**
|
|
514
|
+
* Discover the WSS URL for a tunnel by running `devtunnel show`.
|
|
515
|
+
* Parses the port line to find the HTTPS URL, converts to WSS.
|
|
516
|
+
*/
|
|
517
|
+
discoverWssUrl(tunnelId) {
|
|
518
|
+
console.log(`[AgentPool] discovering WSS URL for "${tunnelId}"...`);
|
|
519
|
+
const output = child_process.execSync(`devtunnel show ${tunnelId}`, {
|
|
520
|
+
encoding: "utf8",
|
|
521
|
+
env: this.wingetEnv(),
|
|
522
|
+
timeout: 1e4
|
|
523
|
+
});
|
|
524
|
+
const portLine = output.split("\n").find((l) => l.includes("9741") && l.includes("https://"));
|
|
525
|
+
if (!portLine) {
|
|
526
|
+
throw new Error(`No port 9741 URL found in devtunnel show output for "${tunnelId}"`);
|
|
527
|
+
}
|
|
528
|
+
const match = portLine.match(/https:\/\/[^\s]+/);
|
|
529
|
+
if (!match) {
|
|
530
|
+
throw new Error(`Could not parse URL from: ${portLine.trim()}`);
|
|
531
|
+
}
|
|
532
|
+
const wssUrl = match[0].replace("https://", "wss://").replace(/\/$/, "");
|
|
533
|
+
console.log(`[AgentPool] discovered: ${wssUrl}`);
|
|
534
|
+
return wssUrl;
|
|
535
|
+
}
|
|
536
|
+
/** Get a ready WebSocket for tunnelId, connecting via WSS URL. */
|
|
511
537
|
async getConnection(tunnelId) {
|
|
512
538
|
const existing = this.pool.get(tunnelId);
|
|
513
539
|
if (existing) {
|
|
514
|
-
if (existing.ready && existing.ws
|
|
540
|
+
if (existing.ready && existing.ws.readyState === WebSocket.OPEN) {
|
|
515
541
|
return existing.ws;
|
|
516
542
|
}
|
|
517
|
-
|
|
518
|
-
|
|
543
|
+
if (!existing.ready) {
|
|
544
|
+
console.log(`[AgentPool] getConnection: "${tunnelId}" — queuing (connection in progress)`);
|
|
545
|
+
return new Promise((resolve) => existing.queue.push(resolve));
|
|
546
|
+
}
|
|
547
|
+
this.pool.delete(tunnelId);
|
|
519
548
|
}
|
|
520
|
-
|
|
549
|
+
let wssUrl;
|
|
550
|
+
try {
|
|
551
|
+
wssUrl = this.discoverWssUrl(tunnelId);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
console.log(`[AgentPool] getConnection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
554
|
+
throw err;
|
|
555
|
+
}
|
|
556
|
+
const entry = { ws: null, wssUrl, ready: false, queue: [] };
|
|
521
557
|
this.pool.set(tunnelId, entry);
|
|
522
|
-
console.log(`[AgentPool] getConnection:
|
|
523
|
-
|
|
524
|
-
entry.proc.stderr?.on("data", (data) => {
|
|
525
|
-
console.log(`[AgentPool] devtunnel stderr: ${data.toString().trim()}`);
|
|
526
|
-
});
|
|
527
|
-
entry.proc.on("error", (err) => {
|
|
528
|
-
console.log(`[AgentPool] devtunnel process error: ${err.message}`);
|
|
529
|
-
this.teardown(tunnelId);
|
|
530
|
-
});
|
|
531
|
-
entry.proc.on("exit", (code) => {
|
|
532
|
-
console.log(`[AgentPool] devtunnel exited with code ${code}`);
|
|
533
|
-
this.teardown(tunnelId);
|
|
534
|
-
});
|
|
535
|
-
console.log(`[AgentPool] getConnection: waiting 4s for devtunnel to establish port forwarding...`);
|
|
536
|
-
await new Promise((r) => setTimeout(r, 4e3));
|
|
537
|
-
console.log(`[AgentPool] getConnection: opening WebSocket to ws://127.0.0.1:9741...`);
|
|
538
|
-
const ws = new WebSocket("ws://127.0.0.1:9741");
|
|
558
|
+
console.log(`[AgentPool] getConnection: opening WebSocket to ${wssUrl}...`);
|
|
559
|
+
const ws = new WebSocket(wssUrl);
|
|
539
560
|
entry.ws = ws;
|
|
540
561
|
return new Promise((resolve, reject) => {
|
|
541
562
|
ws.on("open", () => {
|
|
542
|
-
console.log(`[AgentPool] getConnection:
|
|
563
|
+
console.log(`[AgentPool] getConnection: connected to agent via ${wssUrl}`);
|
|
543
564
|
entry.ready = true;
|
|
544
565
|
entry.queue.forEach((cb) => cb(ws));
|
|
545
566
|
entry.queue = [];
|
|
546
567
|
resolve(ws);
|
|
547
568
|
});
|
|
548
569
|
ws.on("error", (err) => {
|
|
549
|
-
console.log(`[AgentPool]
|
|
570
|
+
console.log(`[AgentPool] WebSocket error for "${tunnelId}": ${err.message}`);
|
|
550
571
|
this.teardown(tunnelId);
|
|
551
572
|
reject(new Error(`Agent WebSocket error: ${err.message}`));
|
|
552
573
|
});
|
|
553
|
-
ws.on("close", () =>
|
|
574
|
+
ws.on("close", () => {
|
|
575
|
+
console.log(`[AgentPool] WebSocket closed for "${tunnelId}"`);
|
|
576
|
+
this.teardown(tunnelId);
|
|
577
|
+
});
|
|
554
578
|
});
|
|
555
579
|
}
|
|
556
580
|
/** Ping the agent via WebSocket, return true if pong received within 5s. */
|
|
557
581
|
async ping(tunnelId) {
|
|
558
582
|
try {
|
|
559
|
-
console.log(`[AgentPool] ping: connecting to
|
|
583
|
+
console.log(`[AgentPool] ping: connecting to "${tunnelId}"...`);
|
|
560
584
|
const ws = await this.getConnection(tunnelId);
|
|
561
585
|
console.log(`[AgentPool] ping: connected, sending ping...`);
|
|
562
586
|
return new Promise((resolve) => {
|
|
@@ -588,10 +612,6 @@ class AgentConnectionPool {
|
|
|
588
612
|
teardown(tunnelId) {
|
|
589
613
|
const entry = this.pool.get(tunnelId);
|
|
590
614
|
if (entry) {
|
|
591
|
-
try {
|
|
592
|
-
entry.proc?.kill();
|
|
593
|
-
} catch {
|
|
594
|
-
}
|
|
595
615
|
try {
|
|
596
616
|
entry.ws?.close();
|
|
597
617
|
} catch {
|
|
@@ -1656,7 +1676,7 @@ class SessionManager {
|
|
|
1656
1676
|
stateEnteredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1657
1677
|
tags: {
|
|
1658
1678
|
...opts.tags,
|
|
1659
|
-
"machine": opts.machineId,
|
|
1679
|
+
"machine": machine?.name ?? opts.machineId,
|
|
1660
1680
|
"provider": opts.provider
|
|
1661
1681
|
},
|
|
1662
1682
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -1723,6 +1743,13 @@ class SessionManager {
|
|
|
1723
1743
|
const entry = this.entries.get(id);
|
|
1724
1744
|
if (entry) entry.session.tags = tags;
|
|
1725
1745
|
}
|
|
1746
|
+
/** Send Ctrl+C to interrupt the running process, then clear the screen */
|
|
1747
|
+
interrupt(id) {
|
|
1748
|
+
const entry = this.entries.get(id);
|
|
1749
|
+
if (!entry) return;
|
|
1750
|
+
entry.adapter.write("");
|
|
1751
|
+
setTimeout(() => entry.adapter.write("\r"), 200);
|
|
1752
|
+
}
|
|
1726
1753
|
sendInput(id, data) {
|
|
1727
1754
|
this.entries.get(id)?.adapter.write(data);
|
|
1728
1755
|
}
|
|
@@ -79,6 +79,7 @@ electron.contextBridge.exposeInMainWorld("maestro", {
|
|
|
79
79
|
rename: (id, name) => electron.ipcRenderer.invoke("sessions:rename", id, name),
|
|
80
80
|
updateTags: (id, tags) => electron.ipcRenderer.invoke("sessions:updateTags", id, tags),
|
|
81
81
|
getBuffer: (id) => electron.ipcRenderer.invoke("sessions:getBuffer", id),
|
|
82
|
+
interrupt: (id) => electron.ipcRenderer.invoke("sessions:interrupt", id),
|
|
82
83
|
sendInput: (id, data) => electron.ipcRenderer.invoke("sessions:sendInput", id, data),
|
|
83
84
|
resize: (id, cols, rows) => electron.ipcRenderer.invoke("sessions:resize", id, cols, rows),
|
|
84
85
|
onData: (cb) => {
|