@ynhcj/xiaoyi 2.5.6 β†’ 2.5.7

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.
@@ -0,0 +1,18 @@
1
+ import type { OpenClawConfig } from "openclaw/dist/plugin-sdk/index.js";
2
+ import type { XiaoYiChannelConfig } from "./types.js";
3
+ type ClawdbotConfig = OpenClawConfig;
4
+ /**
5
+ * Resolve XiaoYi channel configuration from OpenClaw config.
6
+ */
7
+ export declare function resolveXYConfig(cfg: ClawdbotConfig): XiaoYiChannelConfig;
8
+ /**
9
+ * List XiaoYi channel account IDs.
10
+ * Single account mode - always returns ["default"].
11
+ */
12
+ export declare function listXYAccountIds(cfg: ClawdbotConfig): string[];
13
+ /**
14
+ * Get default XiaoYi channel account ID.
15
+ * Single account mode - always returns "default".
16
+ */
17
+ export declare function getDefaultXYAccountId(cfg: ClawdbotConfig): string | undefined;
18
+ export {};
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveXYConfig = resolveXYConfig;
4
+ exports.listXYAccountIds = listXYAccountIds;
5
+ exports.getDefaultXYAccountId = getDefaultXYAccountId;
6
+ /**
7
+ * Resolve XiaoYi channel configuration from OpenClaw config.
8
+ */
9
+ function resolveXYConfig(cfg) {
10
+ const channelConfig = cfg?.channels?.xiaoyi;
11
+ if (!channelConfig) {
12
+ throw new Error("XiaoYi channel configuration not found");
13
+ }
14
+ return channelConfig;
15
+ }
16
+ /**
17
+ * List XiaoYi channel account IDs.
18
+ * Single account mode - always returns ["default"].
19
+ */
20
+ function listXYAccountIds(cfg) {
21
+ const channelConfig = cfg?.channels?.xiaoyi;
22
+ if (!channelConfig || !channelConfig.enabled) {
23
+ return [];
24
+ }
25
+ return ["default"];
26
+ }
27
+ /**
28
+ * Get default XiaoYi channel account ID.
29
+ * Single account mode - always returns "default".
30
+ */
31
+ function getDefaultXYAccountId(cfg) {
32
+ const channelConfig = cfg?.channels?.xiaoyi;
33
+ if (!channelConfig || !channelConfig.enabled) {
34
+ return undefined;
35
+ }
36
+ return "default";
37
+ }
@@ -0,0 +1,94 @@
1
+ import type { XiaoYiChannelConfig, A2ACommand } from "./types.js";
2
+ /**
3
+ * Parameters for sending an A2A response.
4
+ */
5
+ export interface SendA2AResponseParams {
6
+ config: XiaoYiChannelConfig;
7
+ sessionId: string;
8
+ taskId: string;
9
+ messageId: string;
10
+ text?: string;
11
+ append: boolean;
12
+ final: boolean;
13
+ files?: Array<{
14
+ fileName: string;
15
+ fileType: string;
16
+ fileId: string;
17
+ }>;
18
+ }
19
+ /**
20
+ * Send an A2A artifact update response.
21
+ */
22
+ export declare function sendA2AResponse(params: SendA2AResponseParams): Promise<void>;
23
+ /**
24
+ * Parameters for sending a reasoning text update (intermediate, streamed).
25
+ */
26
+ export interface SendReasoningTextUpdateParams {
27
+ config: XiaoYiChannelConfig;
28
+ sessionId: string;
29
+ taskId: string;
30
+ messageId: string;
31
+ text: string;
32
+ append?: boolean;
33
+ }
34
+ /**
35
+ * Send an A2A artifact-update with reasoningText part.
36
+ * Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
37
+ * append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
38
+ */
39
+ export declare function sendReasoningTextUpdate(params: SendReasoningTextUpdateParams): Promise<void>;
40
+ /**
41
+ * Parameters for sending a status update.
42
+ */
43
+ export interface SendStatusUpdateParams {
44
+ config: XiaoYiChannelConfig;
45
+ sessionId: string;
46
+ taskId: string;
47
+ messageId: string;
48
+ text: string;
49
+ state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
50
+ }
51
+ /**
52
+ * Send an A2A task status update.
53
+ * Follows A2A protocol standard format with nested status object.
54
+ */
55
+ export declare function sendStatusUpdate(params: SendStatusUpdateParams): Promise<void>;
56
+ /**
57
+ * Parameters for sending a command.
58
+ */
59
+ export interface SendCommandParams {
60
+ config: XiaoYiChannelConfig;
61
+ sessionId: string;
62
+ taskId: string;
63
+ messageId: string;
64
+ command: A2ACommand;
65
+ }
66
+ /**
67
+ * Send a command as an artifact update (final=false).
68
+ */
69
+ export declare function sendCommand(params: SendCommandParams): Promise<void>;
70
+ /**
71
+ * Parameters for sending a clearContext response.
72
+ */
73
+ export interface SendClearContextResponseParams {
74
+ config: XiaoYiChannelConfig;
75
+ sessionId: string;
76
+ messageId: string;
77
+ }
78
+ /**
79
+ * Send a clearContext response.
80
+ */
81
+ export declare function sendClearContextResponse(params: SendClearContextResponseParams): Promise<void>;
82
+ /**
83
+ * Parameters for sending a tasks/cancel response.
84
+ */
85
+ export interface SendTasksCancelResponseParams {
86
+ config: XiaoYiChannelConfig;
87
+ sessionId: string;
88
+ taskId: string;
89
+ messageId: string;
90
+ }
91
+ /**
92
+ * Send a tasks/cancel response.
93
+ */
94
+ export declare function sendTasksCancelResponse(params: SendTasksCancelResponseParams): Promise<void>;
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendA2AResponse = sendA2AResponse;
4
+ exports.sendReasoningTextUpdate = sendReasoningTextUpdate;
5
+ exports.sendStatusUpdate = sendStatusUpdate;
6
+ exports.sendCommand = sendCommand;
7
+ exports.sendClearContextResponse = sendClearContextResponse;
8
+ exports.sendTasksCancelResponse = sendTasksCancelResponse;
9
+ // OpenClaw β†’ A2A format conversion
10
+ const uuid_1 = require("uuid");
11
+ const xy_client_js_1 = require("./xy-client.js");
12
+ const runtime_js_1 = require("./runtime.js");
13
+ /**
14
+ * Send an A2A artifact update response.
15
+ */
16
+ async function sendA2AResponse(params) {
17
+ const { config, sessionId, taskId, messageId, text, append, final, files } = params;
18
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
19
+ const log = runtime?.log ?? console.log;
20
+ const error = runtime?.error ?? console.error;
21
+ // Build artifact update event
22
+ const artifact = {
23
+ taskId,
24
+ kind: "artifact-update",
25
+ append,
26
+ lastChunk: true,
27
+ final,
28
+ artifact: {
29
+ artifactId: (0, uuid_1.v4)(),
30
+ parts: [],
31
+ },
32
+ };
33
+ // Add text part (even if empty string, to maintain parts structure)
34
+ if (text !== undefined) {
35
+ artifact.artifact.parts.push({
36
+ kind: "text",
37
+ text,
38
+ });
39
+ }
40
+ // Add file parts if provided
41
+ if (files && files.length > 0) {
42
+ artifact.artifact.parts.push({
43
+ kind: "data",
44
+ data: { fileInfo: files },
45
+ });
46
+ }
47
+ // Build JSON-RPC response
48
+ const jsonRpcResponse = {
49
+ jsonrpc: "2.0",
50
+ id: messageId,
51
+ result: artifact,
52
+ };
53
+ // Send via WebSocket
54
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
55
+ const outboundMessage = {
56
+ msgType: "agent_response",
57
+ agentId: config.agentId,
58
+ sessionId,
59
+ taskId,
60
+ msgDetail: JSON.stringify(jsonRpcResponse),
61
+ };
62
+ // πŸ“‹ Log complete response body
63
+ log(`[A2A_RESPONSE] πŸ“€ Sending A2A artifact-update response:`);
64
+ log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
65
+ log(`[A2A_RESPONSE] - taskId: ${taskId}`);
66
+ log(`[A2A_RESPONSE] - messageId: ${messageId}`);
67
+ log(`[A2A_RESPONSE] - append: ${append}`);
68
+ log(`[A2A_RESPONSE] - final: ${final}`);
69
+ log(`[A2A_RESPONSE] - text length: ${text?.length ?? 0}`);
70
+ log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
71
+ log(`[A2A_RESPONSE] πŸ“¦ Complete outbound message:`);
72
+ log(JSON.stringify(outboundMessage, null, 2));
73
+ log(`[A2A_RESPONSE] πŸ“¦ JSON-RPC response body:`);
74
+ log(JSON.stringify(jsonRpcResponse, null, 2));
75
+ await wsManager.sendMessage(sessionId, outboundMessage);
76
+ log(`[A2A_RESPONSE] βœ… Message sent successfully`);
77
+ }
78
+ /**
79
+ * Send an A2A artifact-update with reasoningText part.
80
+ * Used for onToolStart, onToolResult, onReasoningStream, onReasoningEnd, onPartialReply.
81
+ * append=true, final=false, lastChunk=true, text is suffixed with newline for markdown rendering.
82
+ */
83
+ async function sendReasoningTextUpdate(params) {
84
+ const { config, sessionId, taskId, messageId, text, append = true } = params;
85
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
86
+ const log = runtime?.log ?? console.log;
87
+ const error = runtime?.error ?? console.error;
88
+ const artifact = {
89
+ taskId,
90
+ kind: "artifact-update",
91
+ append,
92
+ lastChunk: true,
93
+ final: false,
94
+ artifact: {
95
+ artifactId: (0, uuid_1.v4)(),
96
+ parts: [
97
+ {
98
+ kind: "reasoningText",
99
+ reasoningText: text,
100
+ },
101
+ ],
102
+ },
103
+ };
104
+ const jsonRpcResponse = {
105
+ jsonrpc: "2.0",
106
+ id: messageId,
107
+ result: artifact,
108
+ };
109
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
110
+ const outboundMessage = {
111
+ msgType: "agent_response",
112
+ agentId: config.agentId,
113
+ sessionId,
114
+ taskId,
115
+ msgDetail: JSON.stringify(jsonRpcResponse),
116
+ };
117
+ log(`[REASONING_TEXT] πŸ“€ Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
118
+ await wsManager.sendMessage(sessionId, outboundMessage);
119
+ log(`[REASONING_TEXT] βœ… Sent successfully`);
120
+ }
121
+ /**
122
+ * Send an A2A task status update.
123
+ * Follows A2A protocol standard format with nested status object.
124
+ */
125
+ async function sendStatusUpdate(params) {
126
+ const { config, sessionId, taskId, messageId, text, state } = params;
127
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
128
+ const log = runtime?.log ?? console.log;
129
+ const error = runtime?.error ?? console.error;
130
+ // Build status update event following A2A protocol standard
131
+ const statusUpdate = {
132
+ taskId,
133
+ kind: "status-update",
134
+ final: false, // Status updates should not end the stream
135
+ status: {
136
+ message: {
137
+ role: "agent",
138
+ parts: [
139
+ {
140
+ kind: "text",
141
+ text,
142
+ },
143
+ ],
144
+ },
145
+ state,
146
+ },
147
+ };
148
+ // Build JSON-RPC response
149
+ const jsonRpcResponse = {
150
+ jsonrpc: "2.0",
151
+ id: messageId,
152
+ result: statusUpdate,
153
+ };
154
+ // Send via WebSocket
155
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
156
+ const outboundMessage = {
157
+ msgType: "agent_response",
158
+ agentId: config.agentId,
159
+ sessionId,
160
+ taskId,
161
+ msgDetail: JSON.stringify(jsonRpcResponse),
162
+ };
163
+ // πŸ“‹ Log complete response body
164
+ log(`[A2A_STATUS] πŸ“€ Sending A2A status-update:`);
165
+ log(`[A2A_STATUS] - sessionId: ${sessionId}`);
166
+ log(`[A2A_STATUS] - taskId: ${taskId}`);
167
+ log(`[A2A_STATUS] - messageId: ${messageId}`);
168
+ log(`[A2A_STATUS] - state: ${state}`);
169
+ log(`[A2A_STATUS] - text: "${text}"`);
170
+ log(`[A2A_STATUS] πŸ“¦ Complete outbound message:`);
171
+ log(JSON.stringify(outboundMessage, null, 2));
172
+ log(`[A2A_STATUS] πŸ“¦ JSON-RPC response body:`);
173
+ log(JSON.stringify(jsonRpcResponse, null, 2));
174
+ await wsManager.sendMessage(sessionId, outboundMessage);
175
+ log(`[A2A_STATUS] βœ… Status update sent successfully`);
176
+ }
177
+ /**
178
+ * Send a command as an artifact update (final=false).
179
+ */
180
+ async function sendCommand(params) {
181
+ const { config, sessionId, taskId, messageId, command } = params;
182
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
183
+ const log = runtime?.log ?? console.log;
184
+ const error = runtime?.error ?? console.error;
185
+ // Build artifact update with command as data
186
+ // Wrap command in commands array as per protocol requirement
187
+ const artifact = {
188
+ taskId,
189
+ kind: "artifact-update",
190
+ append: false,
191
+ lastChunk: true,
192
+ final: false, // Commands are not final
193
+ artifact: {
194
+ artifactId: (0, uuid_1.v4)(),
195
+ parts: [
196
+ {
197
+ kind: "data",
198
+ data: {
199
+ commands: [command],
200
+ },
201
+ },
202
+ ],
203
+ },
204
+ };
205
+ // Build JSON-RPC response
206
+ const jsonRpcResponse = {
207
+ jsonrpc: "2.0",
208
+ id: messageId,
209
+ result: artifact,
210
+ };
211
+ // Send via WebSocket
212
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
213
+ const outboundMessage = {
214
+ msgType: "agent_response",
215
+ agentId: config.agentId,
216
+ sessionId,
217
+ taskId,
218
+ msgDetail: JSON.stringify(jsonRpcResponse),
219
+ };
220
+ // πŸ“‹ Log complete response body
221
+ log(`[A2A_COMMAND] πŸ“€ Sending A2A command:`);
222
+ log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
223
+ log(`[A2A_COMMAND] - taskId: ${taskId}`);
224
+ log(`[A2A_COMMAND] - messageId: ${messageId}`);
225
+ log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
226
+ log(`[A2A_COMMAND] πŸ“¦ Complete outbound message:`);
227
+ log(JSON.stringify(outboundMessage, null, 2));
228
+ log(`[A2A_COMMAND] πŸ“¦ JSON-RPC response body:`);
229
+ log(JSON.stringify(jsonRpcResponse, null, 2));
230
+ await wsManager.sendMessage(sessionId, outboundMessage);
231
+ log(`[A2A_COMMAND] βœ… Command sent successfully`);
232
+ }
233
+ /**
234
+ * Send a clearContext response.
235
+ */
236
+ async function sendClearContextResponse(params) {
237
+ const { config, sessionId, messageId } = params;
238
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
239
+ const log = runtime?.log ?? console.log;
240
+ const error = runtime?.error ?? console.error;
241
+ // Build JSON-RPC response for clearContext
242
+ const jsonRpcResponse = {
243
+ jsonrpc: "2.0",
244
+ id: messageId,
245
+ result: {
246
+ status: {
247
+ state: "cleared",
248
+ },
249
+ },
250
+ error: {
251
+ code: 0,
252
+ // Note: Using any to bypass type check as the response format differs from standard A2A types
253
+ message: "",
254
+ },
255
+ };
256
+ // Send via WebSocket
257
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
258
+ const outboundMessage = {
259
+ msgType: "agent_response",
260
+ agentId: config.agentId,
261
+ sessionId,
262
+ taskId: sessionId, // Use sessionId as taskId for clearContext
263
+ msgDetail: JSON.stringify(jsonRpcResponse),
264
+ };
265
+ await wsManager.sendMessage(sessionId, outboundMessage);
266
+ log(`Sent clearContext response: sessionId=${sessionId}`);
267
+ }
268
+ /**
269
+ * Send a tasks/cancel response.
270
+ */
271
+ async function sendTasksCancelResponse(params) {
272
+ const { config, sessionId, taskId, messageId } = params;
273
+ const runtime = (0, runtime_js_1.getXiaoYiRuntime)();
274
+ const log = runtime?.log ?? console.log;
275
+ const error = runtime?.error ?? console.error;
276
+ // Build JSON-RPC response for tasks/cancel
277
+ // Note: Using any to bypass type check as the response format differs from standard A2A types
278
+ const jsonRpcResponse = {
279
+ jsonrpc: "2.0",
280
+ id: messageId,
281
+ result: {
282
+ id: taskId,
283
+ status: {
284
+ state: "canceled",
285
+ },
286
+ },
287
+ error: {
288
+ code: 0,
289
+ message: "",
290
+ },
291
+ };
292
+ // Send via WebSocket
293
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(config);
294
+ const outboundMessage = {
295
+ msgType: "agent_response",
296
+ agentId: config.agentId,
297
+ sessionId,
298
+ taskId,
299
+ msgDetail: JSON.stringify(jsonRpcResponse),
300
+ };
301
+ await wsManager.sendMessage(sessionId, outboundMessage);
302
+ log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
303
+ }
@@ -0,0 +1,17 @@
1
+ import type { RuntimeEnv } from "openclaw/dist/plugin-sdk/index.js";
2
+ export type MonitorXYOpts = {
3
+ config?: any;
4
+ runtime?: RuntimeEnv;
5
+ abortSignal?: AbortSignal;
6
+ accountId?: string;
7
+ setStatus?: (status: {
8
+ lastEventAt?: number;
9
+ lastInboundAt?: number;
10
+ connected?: boolean;
11
+ }) => void;
12
+ };
13
+ /**
14
+ * Monitor XY channel WebSocket connections.
15
+ * Keeps the connection alive until abortSignal is triggered.
16
+ */
17
+ export declare function monitorXYProvider(opts?: MonitorXYOpts): Promise<void>;
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.monitorXYProvider = monitorXYProvider;
4
+ const xy_config_js_1 = require("./xy-config.js");
5
+ const xy_client_js_1 = require("./xy-client.js");
6
+ const xy_bot_js_1 = require("./xy-bot.js");
7
+ /**
8
+ * Per-session serial queue that ensures messages from the same session are processed
9
+ * in arrival order while allowing different sessions to run concurrently.
10
+ * Following feishu/monitor.account.ts pattern.
11
+ */
12
+ function createSessionQueue() {
13
+ const queues = new Map();
14
+ return (sessionId, task) => {
15
+ const prev = queues.get(sessionId) ?? Promise.resolve();
16
+ const next = prev.then(task, task);
17
+ queues.set(sessionId, next);
18
+ void next.finally(() => {
19
+ if (queues.get(sessionId) === next) {
20
+ queues.delete(sessionId);
21
+ }
22
+ });
23
+ return next;
24
+ };
25
+ }
26
+ /**
27
+ * Monitor XY channel WebSocket connections.
28
+ * Keeps the connection alive until abortSignal is triggered.
29
+ */
30
+ async function monitorXYProvider(opts = {}) {
31
+ const cfg = opts.config;
32
+ if (!cfg) {
33
+ throw new Error("Config is required for XY monitor");
34
+ }
35
+ const runtime = opts.runtime;
36
+ const log = runtime?.log ?? console.log;
37
+ const error = runtime?.error ?? console.error;
38
+ const account = (0, xy_config_js_1.resolveXYConfig)(cfg);
39
+ if (!account.enabled) {
40
+ throw new Error(`XY account is disabled`);
41
+ }
42
+ const accountId = opts.accountId ?? "default";
43
+ // Create trackEvent function to report health to OpenClaw framework
44
+ const trackEvent = opts.setStatus
45
+ ? () => {
46
+ opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
47
+ }
48
+ : undefined;
49
+ // πŸ” Diagnose WebSocket managers before gateway start
50
+ // console.log("πŸ” [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
51
+ // diagnoseAllManagers();
52
+ // Get WebSocket manager (cached)
53
+ const wsManager = (0, xy_client_js_1.getXYWebSocketManager)(account);
54
+ // βœ… Set health event callback for heartbeat reporting
55
+ // This ensures OpenClaw's health-monitor sees activity and doesn't trigger stale-socket restarts
56
+ if (trackEvent) {
57
+ wsManager.setHealthEventCallback(trackEvent);
58
+ }
59
+ // Track logged servers to avoid duplicate logs
60
+ const loggedServers = new Set();
61
+ // Track active message processing to detect duplicates
62
+ const activeMessages = new Set();
63
+ // Create session queue for ordered message processing
64
+ const enqueue = createSessionQueue();
65
+ // Health check interval
66
+ let healthCheckInterval = null;
67
+ return new Promise((resolve, reject) => {
68
+ // Event handlers (defined early so they can be referenced in cleanup)
69
+ const messageHandler = (message, sessionId, serverId) => {
70
+ const messageKey = `${sessionId}::${message.id}`;
71
+ log(`[MONITOR-HANDLER] ####### messageHandler triggered: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
72
+ // βœ… Report health: received a message
73
+ trackEvent?.();
74
+ // Check for duplicate message handling
75
+ if (activeMessages.has(messageKey)) {
76
+ error(`[MONITOR-HANDLER] ⚠️ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
77
+ }
78
+ activeMessages.add(messageKey);
79
+ log(`[MONITOR-HANDLER] πŸ“ Active messages count: ${activeMessages.size}, messageKey: ${messageKey}`);
80
+ const task = async () => {
81
+ try {
82
+ log(`[MONITOR-HANDLER] πŸš€ Starting handleXYMessage for messageKey=${messageKey}`);
83
+ await (0, xy_bot_js_1.handleXYMessage)({
84
+ cfg,
85
+ runtime,
86
+ message,
87
+ accountId, // βœ… Pass accountId ("default")
88
+ });
89
+ log(`[MONITOR-HANDLER] βœ… Completed handleXYMessage for messageKey=${messageKey}`);
90
+ }
91
+ catch (err) {
92
+ // βœ… Only log error, don't re-throw to prevent gateway restart
93
+ error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
94
+ }
95
+ finally {
96
+ // Remove from active messages when done
97
+ activeMessages.delete(messageKey);
98
+ log(`[MONITOR-HANDLER] 🧹 Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
99
+ }
100
+ };
101
+ void enqueue(sessionId, task).catch((err) => {
102
+ // Error already logged in task, this is for queue failures
103
+ error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
104
+ activeMessages.delete(messageKey);
105
+ });
106
+ };
107
+ const connectedHandler = (serverId) => {
108
+ if (!loggedServers.has(serverId)) {
109
+ log(`XY gateway: ${serverId} connected`);
110
+ loggedServers.add(serverId);
111
+ }
112
+ // βœ… Report health: connection established
113
+ trackEvent?.();
114
+ opts.setStatus?.({ connected: true });
115
+ };
116
+ const disconnectedHandler = (serverId) => {
117
+ console.warn(`XY gateway: ${serverId} disconnected`);
118
+ loggedServers.delete(serverId);
119
+ // βœ… Report disconnection status (only if all servers disconnected)
120
+ if (loggedServers.size === 0) {
121
+ opts.setStatus?.({ connected: false });
122
+ }
123
+ };
124
+ const errorHandler = (err, serverId) => {
125
+ error(`XY gateway: ${serverId} error: ${String(err)}`);
126
+ };
127
+ const cleanup = () => {
128
+ log("XY gateway: cleaning up...");
129
+ // Stop health check interval
130
+ if (healthCheckInterval) {
131
+ clearInterval(healthCheckInterval);
132
+ healthCheckInterval = null;
133
+ console.log("⏸️ Stopped periodic health check");
134
+ }
135
+ // Remove event handlers to prevent duplicate calls on gateway restart
136
+ wsManager.off("message", messageHandler);
137
+ wsManager.off("connected", connectedHandler);
138
+ wsManager.off("disconnected", disconnectedHandler);
139
+ wsManager.off("error", errorHandler);
140
+ // βœ… Remove manager from cache - this will also disconnect
141
+ // removeXYWebSocketManager internally calls manager.disconnect()
142
+ (0, xy_client_js_1.removeXYWebSocketManager)(account);
143
+ loggedServers.clear();
144
+ activeMessages.clear();
145
+ log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages`);
146
+ };
147
+ const handleAbort = () => {
148
+ log("XY gateway: abort signal received, stopping");
149
+ cleanup();
150
+ log("XY gateway stopped");
151
+ resolve();
152
+ };
153
+ if (opts.abortSignal?.aborted) {
154
+ cleanup();
155
+ resolve();
156
+ return;
157
+ }
158
+ opts.abortSignal?.addEventListener("abort", handleAbort, { once: true });
159
+ // Register event handlers (handlers are defined above in cleanup scope)
160
+ wsManager.on("message", messageHandler);
161
+ wsManager.on("connected", connectedHandler);
162
+ wsManager.on("disconnected", disconnectedHandler);
163
+ wsManager.on("error", errorHandler);
164
+ // Start periodic health check (every 5 minutes)
165
+ console.log("πŸ₯ Starting periodic health check (every 5 minutes)...");
166
+ healthCheckInterval = setInterval(() => {
167
+ console.log("πŸ₯ [HEALTH CHECK] Periodic WebSocket diagnostics...");
168
+ // diagnoseAllManagers();
169
+ // // Auto-cleanup orphan connections
170
+ // const cleaned = cleanupOrphanConnections();
171
+ // if (cleaned > 0) {
172
+ // console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
173
+ // }
174
+ }, 5 * 60 * 1000); // 5 minutes
175
+ // Connect to WebSocket servers
176
+ wsManager.connect()
177
+ .then(() => {
178
+ log("XY gateway: started successfully");
179
+ })
180
+ .catch((err) => {
181
+ // Connection failed but don't reject - continue monitoring for reconnection
182
+ error(`XY gateway: initial connection failed: ${String(err)}`);
183
+ // Still resolve successfully so plugin starts
184
+ resolve();
185
+ });
186
+ });
187
+ }