echoclaw-relay-agent 0.17.2 → 0.19.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.
Files changed (2) hide show
  1. package/dist/cli.js +57 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -255,6 +255,30 @@ async function runSetup(code, relay, bridgePort) {
255
255
  }
256
256
  }
257
257
  catch { /* no service manager on this platform — fine */ }
258
+ // Kill any remaining relay-agent processes not managed by service manager.
259
+ // Use narrow match: only our own binary name, exclude our own PID.
260
+ try {
261
+ const { execSync } = await import('child_process');
262
+ const myPid = process.pid.toString();
263
+ try {
264
+ execSync(`pgrep -f "echoclaw-relay-agent.*--gateway" | grep -v "^${myPid}$" | xargs kill 2>/dev/null`, { stdio: 'ignore' });
265
+ }
266
+ catch { /* no matching processes — fine */ }
267
+ // Poll until processes are gone (max 5s)
268
+ const deadline = Date.now() + 5000;
269
+ while (Date.now() < deadline) {
270
+ try {
271
+ const out = execSync(`pgrep -f "echoclaw-relay-agent.*--gateway" | grep -v "^${myPid}$"`, { stdio: 'pipe' }).toString().trim();
272
+ if (!out)
273
+ break;
274
+ }
275
+ catch {
276
+ break; /* pgrep returns non-zero = no matches */
277
+ }
278
+ await new Promise(r => setTimeout(r, 300));
279
+ }
280
+ }
281
+ catch { /* pgrep not available — fine */ }
258
282
  const sid = existingSession?.relaySessionId?.slice?.(0, 8) ?? 'unknown';
259
283
  console.log(` ${DIM}Clearing previous session (${sid}...)${RESET}`);
260
284
  try {
@@ -645,32 +669,36 @@ async function runDaemon(relay, code, gateway, bridgePort) {
645
669
  agent.on('connected', () => {
646
670
  updater.startPeriodicCheck();
647
671
  });
648
- // Retry logic — transient failures (network, server restart) shouldn't kill the service
649
- const MAX_RETRIES = 5;
650
- const RETRY_DELAY = 3000;
651
- let lastErr;
652
- for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
672
+ // V2: Never-give-up retry logic — daemon must stay alive forever.
673
+ // Only exits on: explicit UNPAIR, SIGINT/SIGTERM, or no saved session + no code.
674
+ const BASE_DELAY = 3000;
675
+ const MAX_DELAY = 60000;
676
+ let attempt = 0;
677
+ while (!stopping) {
653
678
  try {
679
+ attempt++;
654
680
  await agent.start(code);
655
- return; // successstay alive
681
+ // Connectedreset attempt counter and clear code
682
+ // Session is saved after first success; future reconnects use session resume
683
+ attempt = 0;
684
+ code = undefined;
656
685
  }
657
686
  catch (err) {
658
- lastErr = err;
659
687
  const msg = err instanceof Error ? err.message : String(err);
660
- console.error(` [start-failed] attempt=${attempt}/${MAX_RETRIES} error=${msg}`);
661
- // Permanent errors don't retry
662
- if (msg.includes('No saved session found') ||
663
- msg.includes('DEVICE_TOKEN_MISMATCH') ||
664
- msg.includes('SESSION_NOT_FOUND')) {
688
+ console.error(` [start-failed] attempt=${attempt} error=${msg}`);
689
+ // Truly permanent: no session AND no code to pair with
690
+ if (msg.includes('No saved session found') && !code) {
691
+ console.error(` [fatal] No session and no pairing code. Run: echoclaw-relay setup <CODE>`);
665
692
  break;
666
693
  }
667
- if (attempt < MAX_RETRIES) {
668
- console.log(` [retry] waiting ${RETRY_DELAY / 1000}s...`);
669
- await new Promise(r => setTimeout(r, RETRY_DELAY));
670
- }
694
+ // SESSION_NOT_FOUND is NOT permanent in V2 — identity reconnect will handle it
695
+ // Just retry with backoff
696
+ // Exponential backoff: 3s, 6s, 12s, 24s, 48s, 60s (capped)
697
+ const delay = Math.min(BASE_DELAY * Math.pow(2, attempt - 1), MAX_DELAY);
698
+ console.log(` [retry] waiting ${(delay / 1000).toFixed(0)}s...`);
699
+ await new Promise(r => setTimeout(r, delay));
671
700
  }
672
701
  }
673
- throw lastErr;
674
702
  }
675
703
  // ── Main ─────────────────────────────────────────────────────
676
704
  async function main() {
@@ -710,4 +738,16 @@ async function main() {
710
738
  process.exit(1);
711
739
  }
712
740
  }
741
+ // V2: Global error handlers — log and exit, let supervisor (launchd/systemd) restart.
742
+ // Staying alive after uncaughtException risks corrupted state (half-open sockets,
743
+ // partial writes). Clean crash + restart is safer than zombie process.
744
+ process.on('uncaughtException', (err) => {
745
+ console.error(` [FATAL] uncaughtException: ${err.message}`);
746
+ console.error(err.stack || '');
747
+ process.exit(1);
748
+ });
749
+ process.on('unhandledRejection', (reason) => {
750
+ console.error(` [FATAL] unhandledRejection: ${reason}`);
751
+ process.exit(1);
752
+ });
713
753
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echoclaw-relay-agent",
3
- "version": "0.17.2",
3
+ "version": "0.19.0",
4
4
  "description": "EchoClaw Relay Connection — E2E encrypted relay transport, pairing, and session management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",