orchestrating 0.1.36 → 0.1.38
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/bin/orch +24 -0
- package/package.json +1 -1
package/bin/orch
CHANGED
|
@@ -328,6 +328,8 @@ if (firstArg === "daemon") {
|
|
|
328
328
|
</dict>
|
|
329
329
|
</dict>
|
|
330
330
|
</plist>`;
|
|
331
|
+
// Unload old plist first if it exists (avoids "Load failed" error on re-enable)
|
|
332
|
+
try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`); } catch {}
|
|
331
333
|
writeFileSync(plistPath, plist);
|
|
332
334
|
execSync(`launchctl load -w "${plistPath}"`);
|
|
333
335
|
console.log("Daemon enabled — will start on login and auto-restart.");
|
|
@@ -353,6 +355,8 @@ WantedBy=default.target
|
|
|
353
355
|
`;
|
|
354
356
|
writeFileSync(servicePath, service);
|
|
355
357
|
execSync("systemctl --user daemon-reload");
|
|
358
|
+
// Stop old instance if running, then enable + start
|
|
359
|
+
try { execSync("systemctl --user stop orch-daemon.service 2>/dev/null"); } catch {}
|
|
356
360
|
execSync("systemctl --user enable --now orch-daemon.service");
|
|
357
361
|
// Enable lingering so user services run without active login session
|
|
358
362
|
try { execSync(`loginctl enable-linger ${os.userInfo().username}`); } catch {}
|
|
@@ -448,10 +452,12 @@ async function handleDaemon(projectsDir) {
|
|
|
448
452
|
|
|
449
453
|
const PING_INTERVAL_MS = 30_000;
|
|
450
454
|
const PONG_TIMEOUT_MS = 10_000;
|
|
455
|
+
const HEARTBEAT_TIMEOUT_MS = 65_000; // expect server heartbeat every 30s, allow 2 missed + margin
|
|
451
456
|
let ws = null;
|
|
452
457
|
let pingTimer = null;
|
|
453
458
|
let pongTimer = null;
|
|
454
459
|
let reconnectTimer = null;
|
|
460
|
+
let heartbeatTimer = null;
|
|
455
461
|
|
|
456
462
|
// Scan project directories
|
|
457
463
|
const homeDir = os.homedir();
|
|
@@ -478,6 +484,14 @@ async function handleDaemon(projectsDir) {
|
|
|
478
484
|
const recentRequests = new Set();
|
|
479
485
|
const recentSpawns = new Map(); // "command:cwd" -> timestamp
|
|
480
486
|
|
|
487
|
+
function resetHeartbeatTimer(sock) {
|
|
488
|
+
if (heartbeatTimer) clearTimeout(heartbeatTimer);
|
|
489
|
+
heartbeatTimer = setTimeout(() => {
|
|
490
|
+
process.stderr.write(`${RED}${PREFIX} No server heartbeat — reconnecting${RESET}\n`);
|
|
491
|
+
if (sock && sock.readyState !== WebSocket.CLOSED) sock.terminate();
|
|
492
|
+
}, HEARTBEAT_TIMEOUT_MS);
|
|
493
|
+
}
|
|
494
|
+
|
|
481
495
|
function scheduleReconnect(delaySec) {
|
|
482
496
|
if (reconnecting) return;
|
|
483
497
|
reconnecting = true;
|
|
@@ -530,6 +544,9 @@ async function handleDaemon(projectsDir) {
|
|
|
530
544
|
}, PONG_TIMEOUT_MS);
|
|
531
545
|
}
|
|
532
546
|
}, PING_INTERVAL_MS);
|
|
547
|
+
|
|
548
|
+
// Start expecting server heartbeats (more reliable than WS pings through proxies)
|
|
549
|
+
resetHeartbeatTimer(sock);
|
|
533
550
|
});
|
|
534
551
|
|
|
535
552
|
sock.on("pong", () => {
|
|
@@ -540,6 +557,11 @@ async function handleDaemon(projectsDir) {
|
|
|
540
557
|
let msg;
|
|
541
558
|
try { msg = JSON.parse(raw.toString()); } catch { return; }
|
|
542
559
|
|
|
560
|
+
if (msg.type === "heartbeat") {
|
|
561
|
+
resetHeartbeatTimer(sock);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
543
565
|
if (msg.type === "error") {
|
|
544
566
|
process.stderr.write(`${RED}${PREFIX} Server: ${msg.error}${RESET}\n`);
|
|
545
567
|
if (/unauthorized|auth|token/i.test(msg.error || "")) {
|
|
@@ -647,6 +669,7 @@ async function handleDaemon(projectsDir) {
|
|
|
647
669
|
if (ws === sock) ws = null;
|
|
648
670
|
if (pingTimer) { clearInterval(pingTimer); pingTimer = null; }
|
|
649
671
|
if (pongTimer) { clearTimeout(pongTimer); pongTimer = null; }
|
|
672
|
+
if (heartbeatTimer) { clearTimeout(heartbeatTimer); heartbeatTimer = null; }
|
|
650
673
|
process.stderr.write(`${DIM}${PREFIX} Disconnected — reconnecting in 2s${RESET}\n`);
|
|
651
674
|
scheduleReconnect(2);
|
|
652
675
|
});
|
|
@@ -663,6 +686,7 @@ async function handleDaemon(projectsDir) {
|
|
|
663
686
|
process.stderr.write(`\n${DIM}${PREFIX} Shutting down${RESET}\n`);
|
|
664
687
|
if (pingTimer) clearInterval(pingTimer);
|
|
665
688
|
if (pongTimer) clearTimeout(pongTimer);
|
|
689
|
+
if (heartbeatTimer) clearTimeout(heartbeatTimer);
|
|
666
690
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
667
691
|
if (ws) ws.close();
|
|
668
692
|
process.exit(0);
|