echoclaw-relay-agent 0.19.3 → 0.19.4

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.
@@ -602,18 +602,19 @@ export class RelayAgent extends EventEmitter {
602
602
  this._identityExchanged = true;
603
603
  return;
604
604
  }
605
- // Remove early handler before entering wait prevents both handlers from
606
- // processing the same message simultaneously (Reviewer #2, #3 finding).
607
- this.removeListener('message', earlyHandler);
608
- // Use early-captured message if available, otherwise wait with timeout
609
- const peerIdentity = earlyIdentity ?? await this._waitForIdentityExchange(10000);
610
- // Respond with our own identity
605
+ // SYMMETRIC EXCHANGE: Send our identity FIRST, then wait for peer's.
606
+ // Both sides send immediately upon pairing eliminates "who sends first" race.
611
607
  const myPubKeyBase64 = toBase64(ed.publicKeyRaw);
612
608
  await this.send({
613
609
  type: 'identity_exchange',
614
610
  ed25519_pubkey: myPubKeyBase64,
615
611
  device_id: identity.deviceId,
616
612
  });
613
+ // Remove early handler before entering wait — prevents both handlers from
614
+ // processing the same message simultaneously.
615
+ this.removeListener('message', earlyHandler);
616
+ // Use early-captured message if available, otherwise wait with timeout
617
+ const peerIdentity = earlyIdentity ?? await this._waitForIdentityExchange(15000);
617
618
  // Persist peer's public key
618
619
  await this.identityStore.updatePeer(peerIdentity.ed25519_pubkey);
619
620
  sessionData.peerPublicKeyRaw = peerIdentity.ed25519_pubkey;
@@ -286,11 +286,20 @@ export class RelayClient extends EventEmitter {
286
286
  await this.onPaired(result, 'paired');
287
287
  }
288
288
  catch (err) {
289
- // Identity exchange failed on fresh pairing tear down transport to
290
- // prevent leaking an authenticated-but-unusable WebSocket, then re-throw.
291
- // Do NOT emit('error') here the throw propagates to connect() caller.
292
- this.transport?.disconnect();
293
- throw err;
289
+ // Identity exchange failed — non-fatal for fresh pairing.
290
+ // ECDH succeeded, encrypted channel works. Identity exchange only enables
291
+ // V2 identity-based reconnect. Keep connection alive, emit warning.
292
+ const isIdentityErr = err?.message?.includes('Identity exchange');
293
+ if (isIdentityErr) {
294
+ console.warn('[RelayClient] Identity exchange failed (non-fatal):', err?.message);
295
+ this.emit('error', Object.assign(new Error(`Identity exchange failed: ${err?.message}`), { name: 'IdentityExchangeError' }));
296
+ // Connection is usable — don't tear down
297
+ }
298
+ else {
299
+ // Non-identity errors are still fatal
300
+ this.transport?.disconnect();
301
+ throw err;
302
+ }
294
303
  }
295
304
  }
296
305
  async resumeSession(session) {
@@ -577,7 +586,8 @@ export class RelayClient extends EventEmitter {
577
586
  this._identityExchanged = true;
578
587
  return; // finally will clean up earlyHandler
579
588
  }
580
- // Send our Ed25519 public key to the agent (through E2E encrypted DATA channel)
589
+ // SYMMETRIC EXCHANGE: Send our identity FIRST, then wait for peer's.
590
+ // Both sides send immediately upon pairing — eliminates "who sends first" race.
581
591
  const myPubKeyBase64 = toBase64(ed.publicKeyRaw);
582
592
  await this.send({
583
593
  type: 'identity_exchange',
@@ -586,8 +596,8 @@ export class RelayClient extends EventEmitter {
586
596
  });
587
597
  // Remove early handler before entering wait — prevents dual listeners
588
598
  this.removeListener('message', earlyHandler);
589
- // Use early-captured message if available, otherwise wait with timeout
590
- const peerIdentity = earlyIdentity ?? await this._waitForIdentityExchange(10000);
599
+ // Use early-captured message if available, otherwise wait with timeout (15s for setup)
600
+ const peerIdentity = earlyIdentity ?? await this._waitForIdentityExchange(15000);
591
601
  // Persist peer's public key
592
602
  await this.identityStore.updatePeer(peerIdentity.ed25519_pubkey);
593
603
  sessionData.peerPublicKeyRaw = peerIdentity.ed25519_pubkey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "echoclaw-relay-agent",
3
- "version": "0.19.3",
3
+ "version": "0.19.4",
4
4
  "description": "EchoClaw Relay Connection — E2E encrypted relay transport, pairing, and session management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",