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.
Files changed (2) hide show
  1. package/bin/orch +24 -0
  2. 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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrating",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "description": "Stream terminal sessions to the orchestrat.ing dashboard",
5
5
  "type": "module",
6
6
  "bin": {