echoclaw-relay-agent 0.9.3 → 0.9.5
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/dist/RelayAgent.d.ts +1 -0
- package/dist/RelayAgent.js +24 -0
- package/dist/RelayClient.d.ts +1 -0
- package/dist/RelayClient.js +27 -3
- package/dist/chat/ChatHandler.d.ts +4 -1
- package/dist/chat/ChatHandler.js +34 -3
- package/package.json +1 -1
package/dist/RelayAgent.d.ts
CHANGED
package/dist/RelayAgent.js
CHANGED
|
@@ -97,6 +97,12 @@ export class RelayAgent extends EventEmitter {
|
|
|
97
97
|
writable: true,
|
|
98
98
|
value: false
|
|
99
99
|
});
|
|
100
|
+
Object.defineProperty(this, "_rekeyRetryCount", {
|
|
101
|
+
enumerable: true,
|
|
102
|
+
configurable: true,
|
|
103
|
+
writable: true,
|
|
104
|
+
value: 0
|
|
105
|
+
});
|
|
100
106
|
Object.defineProperty(this, "_activePairing", {
|
|
101
107
|
enumerable: true,
|
|
102
108
|
configurable: true,
|
|
@@ -398,6 +404,7 @@ export class RelayAgent extends EventEmitter {
|
|
|
398
404
|
// If desktop is online, ECDH completes in <1s; 30s gives ample margin.
|
|
399
405
|
pairing.pairAsAgent(undefined, 30000)
|
|
400
406
|
.then(result => {
|
|
407
|
+
this._rekeyRetryCount = 0; // Reset on success
|
|
401
408
|
this.onPaired(result);
|
|
402
409
|
// V2: Reattach gateway callbacks after reconnect
|
|
403
410
|
if (this.gatewayManager) {
|
|
@@ -411,11 +418,28 @@ export class RelayAgent extends EventEmitter {
|
|
|
411
418
|
})
|
|
412
419
|
.catch(err => {
|
|
413
420
|
this._activePairing = null;
|
|
421
|
+
this._rekeyRetryCount++;
|
|
422
|
+
// Circuit breaker: after 5 consecutive failures, force full reconnect cycle
|
|
423
|
+
// (disconnect + restart resets the relay server connection from scratch)
|
|
424
|
+
if (this._rekeyRetryCount >= 5) {
|
|
425
|
+
this._rekeyRetryCount = 0; // Reset for next cycle
|
|
426
|
+
this.emit('error', new Error(`Re-key failed 5 times, forcing fresh reconnect`));
|
|
427
|
+
if (this.transport) {
|
|
428
|
+
this.transport.disconnect();
|
|
429
|
+
this.transport.restart();
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
414
433
|
// Re-key failed — keep old crypto intact, mark as connected
|
|
415
434
|
// (data handler still works with old keys if desktop didn't re-key either)
|
|
416
435
|
if (this._paired && this.sessionKey) {
|
|
417
436
|
this.setStatus('connected');
|
|
418
437
|
}
|
|
438
|
+
// If WS is still OPEN, force reconnect to trigger fresh on('open')
|
|
439
|
+
if (this.transport?.isConnected) {
|
|
440
|
+
this.transport.disconnect();
|
|
441
|
+
this.transport.restart();
|
|
442
|
+
}
|
|
419
443
|
this.emit('error', err);
|
|
420
444
|
});
|
|
421
445
|
}
|
package/dist/RelayClient.d.ts
CHANGED
package/dist/RelayClient.js
CHANGED
|
@@ -91,6 +91,12 @@ export class RelayClient extends EventEmitter {
|
|
|
91
91
|
writable: true,
|
|
92
92
|
value: false
|
|
93
93
|
});
|
|
94
|
+
Object.defineProperty(this, "_rekeyRetryCount", {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
configurable: true,
|
|
97
|
+
writable: true,
|
|
98
|
+
value: 0
|
|
99
|
+
});
|
|
94
100
|
Object.defineProperty(this, "_activePairing", {
|
|
95
101
|
enumerable: true,
|
|
96
102
|
configurable: true,
|
|
@@ -371,12 +377,30 @@ export class RelayClient extends EventEmitter {
|
|
|
371
377
|
const pairing = new PairingProtocol(this.transport);
|
|
372
378
|
this._activePairing = pairing;
|
|
373
379
|
pairing.pairAsClient(this.pairingCode)
|
|
374
|
-
.then(result =>
|
|
380
|
+
.then(result => {
|
|
381
|
+
this._rekeyRetryCount = 0; // Reset on success
|
|
382
|
+
this.onPaired(result, 'resumed');
|
|
383
|
+
})
|
|
375
384
|
.catch(err => {
|
|
376
385
|
this._activePairing = null;
|
|
377
|
-
|
|
378
|
-
//
|
|
386
|
+
this._rekeyRetryCount++;
|
|
387
|
+
// Circuit breaker: stop after 5 consecutive re-key failures
|
|
388
|
+
if (this._rekeyRetryCount >= 5) {
|
|
389
|
+
this._needsRekey = false;
|
|
390
|
+
this.emit('error', new Error(`Re-key failed after ${this._rekeyRetryCount} retries, giving up`));
|
|
391
|
+
// Force transport to close and reconnect from scratch
|
|
392
|
+
this.transport?.disconnect();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
// Re-key failed — set _needsRekey back to true for retry.
|
|
379
396
|
this._needsRekey = true;
|
|
397
|
+
// If WS is still OPEN (pairing failed at protocol level, not transport level),
|
|
398
|
+
// _needsRekey won't be consumed until next on('open').
|
|
399
|
+
// Force a reconnect to trigger a fresh on('open').
|
|
400
|
+
if (this.transport?.isConnected) {
|
|
401
|
+
this.transport.disconnect();
|
|
402
|
+
this.transport.restart();
|
|
403
|
+
}
|
|
380
404
|
this.emit('error', err);
|
|
381
405
|
});
|
|
382
406
|
}
|
|
@@ -149,7 +149,10 @@ export declare class ChatHandler {
|
|
|
149
149
|
private _extractText;
|
|
150
150
|
/** Send a message back to desktop via the persistent callback. Buffers if disconnected. */
|
|
151
151
|
private _send;
|
|
152
|
-
/**
|
|
152
|
+
/** Sequential send queue to prevent message reordering between flush and real-time messages. */
|
|
153
|
+
private _sendQueue;
|
|
154
|
+
private _enqueueSend;
|
|
155
|
+
/** Flush buffered messages to desktop after relay reconnects (sequential, preserves order). */
|
|
153
156
|
private _flushMessageBuffer;
|
|
154
157
|
/**
|
|
155
158
|
* Send a message via a captured sendBack reference.
|
package/dist/chat/ChatHandler.js
CHANGED
|
@@ -37,6 +37,8 @@ const RUN_TTL_MS = 10 * 60 * 1000;
|
|
|
37
37
|
const RUN_CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
38
38
|
/** Max buffered messages when sendBack is null (relay disconnected). */
|
|
39
39
|
const MAX_BUFFERED_MESSAGES = 50;
|
|
40
|
+
/** Timeout for a single _sendBack call to prevent queue freeze (5 seconds). */
|
|
41
|
+
const SEND_TIMEOUT_MS = 5000;
|
|
40
42
|
// ── ChatHandler ──────────────────────────────────────────────────
|
|
41
43
|
export class ChatHandler {
|
|
42
44
|
constructor(config) {
|
|
@@ -112,6 +114,13 @@ export class ChatHandler {
|
|
|
112
114
|
writable: true,
|
|
113
115
|
value: []
|
|
114
116
|
});
|
|
117
|
+
/** Sequential send queue to prevent message reordering between flush and real-time messages. */
|
|
118
|
+
Object.defineProperty(this, "_sendQueue", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
configurable: true,
|
|
121
|
+
writable: true,
|
|
122
|
+
value: Promise.resolve()
|
|
123
|
+
});
|
|
115
124
|
this.sessionKey = config?.sessionKey ?? 'main';
|
|
116
125
|
this.requestTimeoutMs = config?.requestTimeoutMs ?? 30000;
|
|
117
126
|
this.wsClient = new OpenClawWsClient({
|
|
@@ -569,7 +578,8 @@ export class ChatHandler {
|
|
|
569
578
|
/** Send a message back to desktop via the persistent callback. Buffers if disconnected. */
|
|
570
579
|
async _send(msg) {
|
|
571
580
|
if (this._sendBack) {
|
|
572
|
-
|
|
581
|
+
// Use send queue to guarantee ordering (flush + real-time share same queue)
|
|
582
|
+
await this._enqueueSend(msg);
|
|
573
583
|
}
|
|
574
584
|
else {
|
|
575
585
|
// Buffer important messages while relay is disconnected (OpenClaw may still be responding)
|
|
@@ -580,13 +590,34 @@ export class ChatHandler {
|
|
|
580
590
|
}
|
|
581
591
|
}
|
|
582
592
|
}
|
|
583
|
-
|
|
593
|
+
_enqueueSend(msg) {
|
|
594
|
+
const p = this._sendQueue.then(async () => {
|
|
595
|
+
if (!this._sendBack) {
|
|
596
|
+
// sendBack was cleared (relay disconnected) while message was queued.
|
|
597
|
+
// Re-buffer important messages to avoid permanent loss.
|
|
598
|
+
if (msg.type === 'chat_reply' || msg.type === 'chat_delta' || msg.type === 'chat_error') {
|
|
599
|
+
if (this._messageBuffer.length < MAX_BUFFERED_MESSAGES) {
|
|
600
|
+
this._messageBuffer.push(msg);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
// Timeout protection: prevent a single hung _sendBack from freezing the entire queue.
|
|
606
|
+
await Promise.race([
|
|
607
|
+
this._sendBack(msg),
|
|
608
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('send timeout')), SEND_TIMEOUT_MS)),
|
|
609
|
+
]);
|
|
610
|
+
}).catch(() => { });
|
|
611
|
+
this._sendQueue = p;
|
|
612
|
+
return p;
|
|
613
|
+
}
|
|
614
|
+
/** Flush buffered messages to desktop after relay reconnects (sequential, preserves order). */
|
|
584
615
|
_flushMessageBuffer() {
|
|
585
616
|
if (!this._sendBack || this._messageBuffer.length === 0)
|
|
586
617
|
return;
|
|
587
618
|
const buf = this._messageBuffer.splice(0);
|
|
588
619
|
for (const msg of buf) {
|
|
589
|
-
this.
|
|
620
|
+
this._enqueueSend(msg);
|
|
590
621
|
}
|
|
591
622
|
}
|
|
592
623
|
/**
|
package/package.json
CHANGED