@ynhcj/xiaoyi-channel 0.0.70-next → 0.0.71-next
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/src/message-queue.d.ts +17 -0
- package/dist/src/message-queue.js +51 -0
- package/dist/src/monitor.js +32 -3
- package/dist/src/task-manager.d.ts +4 -0
- package/dist/src/task-manager.js +6 -0
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OutboundWebSocketMessage } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Simple message queue for buffering outbound WebSocket messages
|
|
4
|
+
* during disconnection and reconnection stabilization period.
|
|
5
|
+
*/
|
|
6
|
+
export declare class MessageQueue {
|
|
7
|
+
private items;
|
|
8
|
+
private log;
|
|
9
|
+
constructor(log?: (msg: string, ...args: any[]) => void);
|
|
10
|
+
/** Enqueue a message. Drops oldest if over limit. */
|
|
11
|
+
enqueue(message: OutboundWebSocketMessage): void;
|
|
12
|
+
/** Flush all queued messages by calling sendFn for each, then clear. */
|
|
13
|
+
flush(sendFn: (message: OutboundWebSocketMessage) => void): void;
|
|
14
|
+
/** Clear all queued messages without sending. */
|
|
15
|
+
clear(): void;
|
|
16
|
+
get size(): number;
|
|
17
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const MAX_QUEUE_SIZE = 1000;
|
|
2
|
+
/**
|
|
3
|
+
* Simple message queue for buffering outbound WebSocket messages
|
|
4
|
+
* during disconnection and reconnection stabilization period.
|
|
5
|
+
*/
|
|
6
|
+
export class MessageQueue {
|
|
7
|
+
items = [];
|
|
8
|
+
log;
|
|
9
|
+
constructor(log) {
|
|
10
|
+
this.log = log ?? console.log;
|
|
11
|
+
}
|
|
12
|
+
/** Enqueue a message. Drops oldest if over limit. */
|
|
13
|
+
enqueue(message) {
|
|
14
|
+
if (this.items.length >= MAX_QUEUE_SIZE) {
|
|
15
|
+
this.log(`[MessageQueue] Queue full (${MAX_QUEUE_SIZE}), dropping oldest message`);
|
|
16
|
+
this.items.shift();
|
|
17
|
+
}
|
|
18
|
+
this.items.push(message);
|
|
19
|
+
this.log(`[MessageQueue] Enqueued message, queue size: ${this.items.length}`);
|
|
20
|
+
}
|
|
21
|
+
/** Flush all queued messages by calling sendFn for each, then clear. */
|
|
22
|
+
flush(sendFn) {
|
|
23
|
+
const count = this.items.length;
|
|
24
|
+
if (count === 0) {
|
|
25
|
+
this.log("[MessageQueue] Queue empty, nothing to flush");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.log(`[MessageQueue] Flushing ${count} queued messages`);
|
|
29
|
+
for (const msg of this.items) {
|
|
30
|
+
try {
|
|
31
|
+
sendFn(msg);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
this.log(`[MessageQueue] Error flushing message: ${err}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.items = [];
|
|
38
|
+
this.log(`[MessageQueue] Flush complete`);
|
|
39
|
+
}
|
|
40
|
+
/** Clear all queued messages without sending. */
|
|
41
|
+
clear() {
|
|
42
|
+
const count = this.items.length;
|
|
43
|
+
this.items = [];
|
|
44
|
+
if (count > 0) {
|
|
45
|
+
this.log(`[MessageQueue] Cleared ${count} messages`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
get size() {
|
|
49
|
+
return this.items.length;
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { resolveXYConfig } from "./config.js";
|
|
|
2
2
|
import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
|
|
3
3
|
import { handleXYMessage } from "./bot.js";
|
|
4
4
|
import { parseA2AMessage } from "./parser.js";
|
|
5
|
-
import { hasActiveTask } from "./task-manager.js";
|
|
5
|
+
import { hasActiveTask, getAllActiveTaskBindings } from "./task-manager.js";
|
|
6
|
+
import { sendA2AResponse } from "./formatter.js";
|
|
6
7
|
import { handleTriggerEvent } from "./trigger-handler.js";
|
|
7
8
|
import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
|
|
8
9
|
import { handleLoginTokenEvent } from "./login-token-handler.js";
|
|
@@ -204,8 +205,36 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
204
205
|
console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
|
|
205
206
|
diagnoseAllManagers();
|
|
206
207
|
};
|
|
207
|
-
const handleAbort = () => {
|
|
208
|
-
log("XY gateway: abort signal received, stopping");
|
|
208
|
+
const handleAbort = async () => {
|
|
209
|
+
log("XY gateway: abort signal received, sending notifications before stopping");
|
|
210
|
+
// 📤 Send restart notification to all active sessions before disconnecting
|
|
211
|
+
try {
|
|
212
|
+
const activeBindings = getAllActiveTaskBindings();
|
|
213
|
+
if (activeBindings.length > 0) {
|
|
214
|
+
const config = resolveXYConfig(cfg);
|
|
215
|
+
const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
|
|
216
|
+
log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
|
|
217
|
+
const sendPromises = activeBindings.map(binding => sendA2AResponse({
|
|
218
|
+
config,
|
|
219
|
+
sessionId: binding.sessionId,
|
|
220
|
+
taskId: binding.currentTaskId,
|
|
221
|
+
messageId: binding.currentMessageId,
|
|
222
|
+
text: notificationText,
|
|
223
|
+
append: false,
|
|
224
|
+
final: true,
|
|
225
|
+
}).catch(err => {
|
|
226
|
+
error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
|
|
227
|
+
}));
|
|
228
|
+
await Promise.all(sendPromises);
|
|
229
|
+
log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.length} session(s)`);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
log(`[MONITOR] No active sessions, skipping restart notifications`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
error(`[MONITOR] Error sending restart notifications: ${String(err)}`);
|
|
237
|
+
}
|
|
209
238
|
cleanup();
|
|
210
239
|
log("XY gateway stopped");
|
|
211
240
|
resolve();
|
|
@@ -48,6 +48,10 @@ export declare function hasActiveTask(sessionId: string): boolean;
|
|
|
48
48
|
* 获取完整的binding信息(用于调试)
|
|
49
49
|
*/
|
|
50
50
|
export declare function getTaskIdBinding(sessionId: string): TaskIdBinding | null;
|
|
51
|
+
/**
|
|
52
|
+
* 获取所有活跃的 task bindings(用于 gateway_stop 通知等场景)
|
|
53
|
+
*/
|
|
54
|
+
export declare function getAllActiveTaskBindings(): TaskIdBinding[];
|
|
51
55
|
/**
|
|
52
56
|
* 强制清理(错误恢复用)
|
|
53
57
|
*/
|
package/dist/src/task-manager.js
CHANGED
|
@@ -127,6 +127,12 @@ export function hasActiveTask(sessionId) {
|
|
|
127
127
|
export function getTaskIdBinding(sessionId) {
|
|
128
128
|
return activeTaskIds.get(sessionId) ?? null;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* 获取所有活跃的 task bindings(用于 gateway_stop 通知等场景)
|
|
132
|
+
*/
|
|
133
|
+
export function getAllActiveTaskBindings() {
|
|
134
|
+
return Array.from(activeTaskIds.values());
|
|
135
|
+
}
|
|
130
136
|
/**
|
|
131
137
|
* 强制清理(错误恢复用)
|
|
132
138
|
*/
|
package/dist/src/websocket.d.ts
CHANGED
|
@@ -46,6 +46,9 @@ export declare class XYWebSocketManager extends EventEmitter {
|
|
|
46
46
|
private heartbeat;
|
|
47
47
|
private reconnectTimer;
|
|
48
48
|
private isShuttingDown;
|
|
49
|
+
private messageQueue;
|
|
50
|
+
private isBuffering;
|
|
51
|
+
private reconnectBufferTimer;
|
|
49
52
|
private log;
|
|
50
53
|
private error;
|
|
51
54
|
private onHealthEvent?;
|
package/dist/src/websocket.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import WebSocket from "ws";
|
|
3
3
|
import { EventEmitter } from "events";
|
|
4
4
|
import { HeartbeatManager } from "./heartbeat.js";
|
|
5
|
+
import { MessageQueue } from "./message-queue.js";
|
|
5
6
|
/**
|
|
6
7
|
* Manages single WebSocket connection to XY server.
|
|
7
8
|
*
|
|
@@ -28,6 +29,10 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
28
29
|
heartbeat = null;
|
|
29
30
|
reconnectTimer = null;
|
|
30
31
|
isShuttingDown = false;
|
|
32
|
+
// Message queue for buffering during disconnection/reconnection
|
|
33
|
+
messageQueue;
|
|
34
|
+
isBuffering = false;
|
|
35
|
+
reconnectBufferTimer = null;
|
|
31
36
|
// Logging functions
|
|
32
37
|
log;
|
|
33
38
|
error;
|
|
@@ -39,6 +44,7 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
39
44
|
this.runtime = runtime;
|
|
40
45
|
this.log = runtime?.log ?? console.log;
|
|
41
46
|
this.error = runtime?.error ?? console.error;
|
|
47
|
+
this.messageQueue = new MessageQueue(this.log);
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* Set health event callback to report activity to OpenClaw framework.
|
|
@@ -85,6 +91,13 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
85
91
|
clearTimeout(this.reconnectTimer);
|
|
86
92
|
this.reconnectTimer = null;
|
|
87
93
|
}
|
|
94
|
+
// Clear message queue on explicit disconnect (not during reconnection)
|
|
95
|
+
if (this.reconnectBufferTimer) {
|
|
96
|
+
clearTimeout(this.reconnectBufferTimer);
|
|
97
|
+
this.reconnectBufferTimer = null;
|
|
98
|
+
}
|
|
99
|
+
this.messageQueue.clear();
|
|
100
|
+
this.isBuffering = false;
|
|
88
101
|
this.cleanupConnection();
|
|
89
102
|
this.log("Disconnected from XY WebSocket server");
|
|
90
103
|
}
|
|
@@ -92,6 +105,10 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
92
105
|
* Send a message to the server.
|
|
93
106
|
*/
|
|
94
107
|
async sendMessage(sessionId, message) {
|
|
108
|
+
if (this.isBuffering) {
|
|
109
|
+
this.messageQueue.enqueue(message);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
95
112
|
if (!this.ws || !this.state.ready || this.ws.readyState !== WebSocket.OPEN) {
|
|
96
113
|
throw new Error("WebSocket not ready");
|
|
97
114
|
}
|
|
@@ -185,6 +202,11 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
185
202
|
clearTimeout(this.reconnectTimer);
|
|
186
203
|
this.reconnectTimer = null;
|
|
187
204
|
}
|
|
205
|
+
// Clear reconnect buffer timer (but keep message queue for reconnection)
|
|
206
|
+
if (this.reconnectBufferTimer) {
|
|
207
|
+
clearTimeout(this.reconnectBufferTimer);
|
|
208
|
+
this.reconnectBufferTimer = null;
|
|
209
|
+
}
|
|
188
210
|
// Clean up WebSocket
|
|
189
211
|
if (this.ws) {
|
|
190
212
|
// Remove all event listeners
|
|
@@ -282,6 +304,24 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
282
304
|
// Mark as ready after init
|
|
283
305
|
this.state.ready = true;
|
|
284
306
|
this.emit("ready");
|
|
307
|
+
// Start 10-second buffer period after reconnection
|
|
308
|
+
if (this.isBuffering) {
|
|
309
|
+
this.log("[MessageQueue] Reconnected, starting 10s buffer period before flushing queue");
|
|
310
|
+
// Clear any existing buffer timer
|
|
311
|
+
if (this.reconnectBufferTimer) {
|
|
312
|
+
clearTimeout(this.reconnectBufferTimer);
|
|
313
|
+
}
|
|
314
|
+
this.reconnectBufferTimer = setTimeout(() => {
|
|
315
|
+
this.reconnectBufferTimer = null;
|
|
316
|
+
this.messageQueue.flush((msg) => {
|
|
317
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
318
|
+
this.ws.send(JSON.stringify(msg));
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
this.isBuffering = false;
|
|
322
|
+
this.log("[MessageQueue] Buffer period ended, resumed direct sending");
|
|
323
|
+
}, 10000);
|
|
324
|
+
}
|
|
285
325
|
// Start heartbeat
|
|
286
326
|
this.startHeartbeat();
|
|
287
327
|
}
|
|
@@ -509,6 +549,8 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
509
549
|
}
|
|
510
550
|
this.state.connected = false;
|
|
511
551
|
this.state.ready = false;
|
|
552
|
+
// Start buffering messages during disconnection
|
|
553
|
+
this.isBuffering = true;
|
|
512
554
|
this.emit("disconnected");
|
|
513
555
|
// Clean up
|
|
514
556
|
if (this.heartbeat) {
|