@ynhcj/xiaoyi 2.5.5 → 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.
Files changed (43) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/channel.d.ts +116 -14
  3. package/dist/channel.js +199 -665
  4. package/dist/config-schema.d.ts +8 -8
  5. package/dist/config-schema.js +5 -5
  6. package/dist/file-download.d.ts +17 -0
  7. package/dist/file-download.js +69 -0
  8. package/dist/heartbeat.d.ts +39 -0
  9. package/dist/heartbeat.js +102 -0
  10. package/dist/index.d.ts +1 -4
  11. package/dist/index.js +7 -11
  12. package/dist/push.d.ts +28 -0
  13. package/dist/push.js +135 -0
  14. package/dist/runtime.d.ts +48 -2
  15. package/dist/runtime.js +117 -3
  16. package/dist/types.d.ts +95 -1
  17. package/dist/websocket.d.ts +49 -1
  18. package/dist/websocket.js +279 -20
  19. package/dist/xy-bot.d.ts +19 -0
  20. package/dist/xy-bot.js +277 -0
  21. package/dist/xy-client.d.ts +26 -0
  22. package/dist/xy-client.js +78 -0
  23. package/dist/xy-config.d.ts +18 -0
  24. package/dist/xy-config.js +37 -0
  25. package/dist/xy-formatter.d.ts +94 -0
  26. package/dist/xy-formatter.js +303 -0
  27. package/dist/xy-monitor.d.ts +17 -0
  28. package/dist/xy-monitor.js +187 -0
  29. package/dist/xy-parser.d.ts +49 -0
  30. package/dist/xy-parser.js +109 -0
  31. package/dist/xy-reply-dispatcher.d.ts +17 -0
  32. package/dist/xy-reply-dispatcher.js +308 -0
  33. package/dist/xy-tools/session-manager.d.ts +29 -0
  34. package/dist/xy-tools/session-manager.js +80 -0
  35. package/dist/xy-utils/config-manager.d.ts +26 -0
  36. package/dist/xy-utils/config-manager.js +61 -0
  37. package/dist/xy-utils/crypto.d.ts +8 -0
  38. package/dist/xy-utils/crypto.js +21 -0
  39. package/dist/xy-utils/logger.d.ts +6 -0
  40. package/dist/xy-utils/logger.js +37 -0
  41. package/dist/xy-utils/session.d.ts +34 -0
  42. package/dist/xy-utils/session.js +55 -0
  43. package/package.json +32 -16
package/dist/websocket.js CHANGED
@@ -7,8 +7,9 @@ exports.XiaoYiWebSocketManager = void 0;
7
7
  const ws_1 = __importDefault(require("ws"));
8
8
  const events_1 = require("events");
9
9
  const url_1 = require("url");
10
- const auth_1 = require("./auth");
11
- const types_1 = require("./types");
10
+ const auth_js_1 = require("./auth.js");
11
+ const heartbeat_js_1 = require("./heartbeat.js");
12
+ const types_js_1 = require("./types.js");
12
13
  class XiaoYiWebSocketManager extends events_1.EventEmitter {
13
14
  constructor(config) {
14
15
  super();
@@ -30,15 +31,26 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
30
31
  };
31
32
  // ==================== Session → Server Mapping ====================
32
33
  this.sessionServerMap = new Map();
34
+ // ==================== Session Cleanup State ====================
35
+ // Track sessions that are pending cleanup (user cleared context but task still running)
36
+ this.sessionCleanupStateMap = new Map();
33
37
  // ==================== Active Tasks ====================
34
38
  this.activeTasks = new Map();
35
39
  // Resolve configuration with defaults and backward compatibility
36
40
  this.config = this.resolveConfig(config);
37
- this.auth = new auth_1.XiaoYiAuth(this.config.ak, this.config.sk, this.config.agentId);
41
+ this.auth = new auth_js_1.XiaoYiAuth(this.config.ak, this.config.sk, this.config.agentId);
38
42
  console.log(`[WS Manager] Initialized with dual server:`);
39
43
  console.log(` Server 1: ${this.config.wsUrl1}`);
40
44
  console.log(` Server 2: ${this.config.wsUrl2}`);
41
45
  }
46
+ /**
47
+ * Set health event callback to report activity to OpenClaw framework.
48
+ * This callback is invoked on heartbeat success to update lastEventAt.
49
+ */
50
+ setHealthEventCallback(callback) {
51
+ this.onHealthEvent = callback;
52
+ console.log("[WS Manager] Health event callback registered");
53
+ }
42
54
  /**
43
55
  * Check if URL is wss + IP format (skip certificate verification)
44
56
  */
@@ -93,12 +105,12 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
93
105
  }
94
106
  // Apply defaults if not provided
95
107
  if (!wsUrl1) {
96
- console.warn(`[WS Manager] wsUrl1 not provided, using default: ${types_1.DEFAULT_WS_URL_1}`);
97
- wsUrl1 = types_1.DEFAULT_WS_URL_1;
108
+ console.warn(`[WS Manager] wsUrl1 not provided, using default: ${types_js_1.DEFAULT_WS_URL_1}`);
109
+ wsUrl1 = types_js_1.DEFAULT_WS_URL_1;
98
110
  }
99
111
  if (!wsUrl2) {
100
- console.warn(`[WS Manager] wsUrl2 not provided, using default: ${types_1.DEFAULT_WS_URL_2}`);
101
- wsUrl2 = types_1.DEFAULT_WS_URL_2;
112
+ console.warn(`[WS Manager] wsUrl2 not provided, using default: ${types_js_1.DEFAULT_WS_URL_2}`);
113
+ wsUrl2 = types_js_1.DEFAULT_WS_URL_2;
102
114
  }
103
115
  return {
104
116
  wsUrl1,
@@ -107,6 +119,7 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
107
119
  ak: userConfig.ak,
108
120
  sk: userConfig.sk,
109
121
  enableStreaming: userConfig.enableStreaming ?? true,
122
+ sessionCleanupTimeoutMs: userConfig.sessionCleanupTimeoutMs ?? XiaoYiWebSocketManager.DEFAULT_CLEANUP_TIMEOUT_MS,
110
123
  };
111
124
  }
112
125
  /**
@@ -137,6 +150,22 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
137
150
  async connectToServer1() {
138
151
  console.log(`[Server1] Connecting to ${this.config.wsUrl1}...`);
139
152
  try {
153
+ // ✅ Close existing connection and heartbeat before creating new one
154
+ if (this.ws1) {
155
+ console.log(`[Server1] Closing existing connection before reconnect`);
156
+ if (this.heartbeat1) {
157
+ this.heartbeat1.stop();
158
+ this.heartbeat1 = undefined;
159
+ }
160
+ try {
161
+ this.ws1.removeAllListeners();
162
+ this.ws1.close();
163
+ }
164
+ catch (err) {
165
+ console.warn(`[Server1] Error closing old connection:`, err);
166
+ }
167
+ this.ws1 = null;
168
+ }
140
169
  const authHeaders = this.auth.generateAuthHeaders();
141
170
  // Check if URL is wss + IP format, skip certificate verification
142
171
  const skipCertVerify = this.isWssWithIp(this.config.wsUrl1);
@@ -147,6 +176,26 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
147
176
  headers: authHeaders,
148
177
  rejectUnauthorized: !skipCertVerify,
149
178
  });
179
+ // ✅ Initialize HeartbeatManager for server1
180
+ this.heartbeat1 = new heartbeat_js_1.HeartbeatManager(this.ws1, {
181
+ interval: 30000, // 30 seconds
182
+ timeout: 10000, // 10 seconds timeout
183
+ message: JSON.stringify({
184
+ msgType: "heartbeat",
185
+ agentId: this.config.agentId,
186
+ timestamp: Date.now(),
187
+ }),
188
+ }, () => {
189
+ console.log(`[Server1] Heartbeat timeout, reconnecting...`);
190
+ if (this.ws1 && (this.ws1.readyState === ws_1.default.OPEN || this.ws1.readyState === ws_1.default.CONNECTING)) {
191
+ this.ws1.close();
192
+ }
193
+ }, "server1", console.log, console.error, () => {
194
+ // ✅ Heartbeat success callback - report health to OpenClaw
195
+ this.emit("heartbeat", "server1");
196
+ // ✅ Report liveness to OpenClaw framework to prevent stale-socket detection
197
+ this.onHealthEvent?.();
198
+ });
150
199
  this.setupWebSocketHandlers(this.ws1, 'server1');
151
200
  await new Promise((resolve, reject) => {
152
201
  const timeout = setTimeout(() => reject(new Error("Connection timeout")), 30000);
@@ -167,8 +216,9 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
167
216
  this.scheduleStableConnectionCheck('server1');
168
217
  // Send init message
169
218
  this.sendInitMessage(this.ws1, 'server1');
170
- // Start protocol heartbeat
171
- this.startProtocolHeartbeat('server1');
219
+ // Start heartbeat (replaces old startProtocolHeartbeat)
220
+ this.heartbeat1.start();
221
+ console.log(`[Server1] Heartbeat started (30s interval, 10s timeout)`);
172
222
  }
173
223
  catch (error) {
174
224
  console.error(`[Server1] Connection failed:`, error);
@@ -184,6 +234,22 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
184
234
  async connectToServer2() {
185
235
  console.log(`[Server2] Connecting to ${this.config.wsUrl2}...`);
186
236
  try {
237
+ // ✅ Close existing connection and heartbeat before creating new one
238
+ if (this.ws2) {
239
+ console.log(`[Server2] Closing existing connection before reconnect`);
240
+ if (this.heartbeat2) {
241
+ this.heartbeat2.stop();
242
+ this.heartbeat2 = undefined;
243
+ }
244
+ try {
245
+ this.ws2.removeAllListeners();
246
+ this.ws2.close();
247
+ }
248
+ catch (err) {
249
+ console.warn(`[Server2] Error closing old connection:`, err);
250
+ }
251
+ this.ws2 = null;
252
+ }
187
253
  const authHeaders = this.auth.generateAuthHeaders();
188
254
  // Check if URL is wss + IP format, skip certificate verification
189
255
  const skipCertVerify = this.isWssWithIp(this.config.wsUrl2);
@@ -194,6 +260,26 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
194
260
  headers: authHeaders,
195
261
  rejectUnauthorized: !skipCertVerify,
196
262
  });
263
+ // ✅ Initialize HeartbeatManager for server2
264
+ this.heartbeat2 = new heartbeat_js_1.HeartbeatManager(this.ws2, {
265
+ interval: 30000, // 30 seconds
266
+ timeout: 10000, // 10 seconds timeout
267
+ message: JSON.stringify({
268
+ msgType: "heartbeat",
269
+ agentId: this.config.agentId,
270
+ timestamp: Date.now(),
271
+ }),
272
+ }, () => {
273
+ console.log(`[Server2] Heartbeat timeout, reconnecting...`);
274
+ if (this.ws2 && (this.ws2.readyState === ws_1.default.OPEN || this.ws2.readyState === ws_1.default.CONNECTING)) {
275
+ this.ws2.close();
276
+ }
277
+ }, "server2", console.log, console.error, () => {
278
+ // ✅ Heartbeat success callback - report health to OpenClaw
279
+ this.emit("heartbeat", "server2");
280
+ // ✅ Report liveness to OpenClaw framework to prevent stale-socket detection
281
+ this.onHealthEvent?.();
282
+ });
197
283
  this.setupWebSocketHandlers(this.ws2, 'server2');
198
284
  await new Promise((resolve, reject) => {
199
285
  const timeout = setTimeout(() => reject(new Error("Connection timeout")), 30000);
@@ -214,8 +300,9 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
214
300
  this.scheduleStableConnectionCheck('server2');
215
301
  // Send init message
216
302
  this.sendInitMessage(this.ws2, 'server2');
217
- // Start protocol heartbeat
218
- this.startProtocolHeartbeat('server2');
303
+ // Start heartbeat (replaces old startProtocolHeartbeat)
304
+ this.heartbeat2.start();
305
+ console.log(`[Server2] Heartbeat started (30s interval, 10s timeout)`);
219
306
  }
220
307
  catch (error) {
221
308
  console.error(`[Server2] Connection failed:`, error);
@@ -231,12 +318,42 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
231
318
  disconnect() {
232
319
  console.log("[WS Manager] Disconnecting from all servers...");
233
320
  this.clearTimers();
321
+ // ✅ Stop heartbeat managers
322
+ if (this.heartbeat1) {
323
+ console.log("[Server1] Stopping heartbeat manager");
324
+ this.heartbeat1.stop();
325
+ this.heartbeat1 = undefined;
326
+ }
327
+ if (this.heartbeat2) {
328
+ console.log("[Server2] Stopping heartbeat manager");
329
+ this.heartbeat2.stop();
330
+ this.heartbeat2 = undefined;
331
+ }
332
+ // ✅ Properly cleanup WebSocket connections to prevent ghost connections
234
333
  if (this.ws1) {
235
- this.ws1.close();
334
+ try {
335
+ console.log("[Server1] Removing all listeners and closing connection");
336
+ this.ws1.removeAllListeners();
337
+ if (this.ws1.readyState === ws_1.default.OPEN || this.ws1.readyState === ws_1.default.CONNECTING) {
338
+ this.ws1.close();
339
+ }
340
+ }
341
+ catch (err) {
342
+ console.warn("[Server1] Error during disconnect:", err);
343
+ }
236
344
  this.ws1 = null;
237
345
  }
238
346
  if (this.ws2) {
239
- this.ws2.close();
347
+ try {
348
+ console.log("[Server2] Removing all listeners and closing connection");
349
+ this.ws2.removeAllListeners();
350
+ if (this.ws2.readyState === ws_1.default.OPEN || this.ws2.readyState === ws_1.default.CONNECTING) {
351
+ this.ws2.close();
352
+ }
353
+ }
354
+ catch (err) {
355
+ console.warn("[Server2] Error during disconnect:", err);
356
+ }
240
357
  this.ws2 = null;
241
358
  }
242
359
  this.state1.connected = false;
@@ -245,7 +362,15 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
245
362
  this.state2.ready = false;
246
363
  this.sessionServerMap.clear();
247
364
  this.activeTasks.clear();
365
+ // Cleanup session cleanup state map
366
+ for (const [sessionId, state] of this.sessionCleanupStateMap.entries()) {
367
+ if (state.cleanupTimeoutId) {
368
+ clearTimeout(state.cleanupTimeoutId);
369
+ }
370
+ }
371
+ this.sessionCleanupStateMap.clear();
248
372
  this.emit("disconnected");
373
+ console.log("[WS Manager] Disconnect complete");
249
374
  }
250
375
  /**
251
376
  * Send init message to specific server
@@ -382,7 +507,14 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
382
507
  /**
383
508
  * Send A2A response message with automatic routing
384
509
  */
385
- async sendResponse(response, taskId, sessionId, isFinal = true, append = false) {
510
+ async sendResponse(response, taskId, sessionId, isFinal = true, append = true) {
511
+ // Check if session is pending cleanup
512
+ const cleanupState = this.sessionCleanupStateMap.get(sessionId);
513
+ if (cleanupState) {
514
+ // Session is pending cleanup, silently discard response
515
+ console.log(`[RESPONSE] Discarding response for pending cleanup session ${sessionId}`);
516
+ return;
517
+ }
386
518
  // Find which server this session belongs to
387
519
  const targetServer = this.sessionServerMap.get(sessionId);
388
520
  if (!targetServer) {
@@ -461,6 +593,13 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
461
593
  * This uses "status-update" event type which keeps the conversation active
462
594
  */
463
595
  async sendStatusUpdate(taskId, sessionId, message, targetServer) {
596
+ // Check if session is pending cleanup
597
+ const cleanupState = this.sessionCleanupStateMap.get(sessionId);
598
+ if (cleanupState) {
599
+ // Session is pending cleanup, silently discard status updates
600
+ console.log(`[STATUS] Discarding status update for pending cleanup session ${sessionId}`);
601
+ return;
602
+ }
464
603
  const serverId = targetServer || this.sessionServerMap.get(sessionId);
465
604
  if (!serverId) {
466
605
  console.error(`[STATUS] Unknown server for session ${sessionId}`);
@@ -535,6 +674,42 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
535
674
  // TODO: Implement actual push message sending via HTTP API
536
675
  // Need to confirm correct push message format with XiaoYi API documentation
537
676
  }
677
+ /**
678
+ * Send an outbound WebSocket message directly.
679
+ * This is a low-level method that sends a pre-formatted OutboundWebSocketMessage.
680
+ *
681
+ * @param sessionId - Session ID for routing
682
+ * @param message - Pre-formatted outbound message
683
+ */
684
+ async sendMessage(sessionId, message) {
685
+ // Check if session is pending cleanup
686
+ const cleanupState = this.sessionCleanupStateMap.get(sessionId);
687
+ if (cleanupState) {
688
+ console.log(`[SEND_MESSAGE] Discarding message for pending cleanup session ${sessionId}`);
689
+ return;
690
+ }
691
+ // Find which server this session belongs to
692
+ const targetServer = this.sessionServerMap.get(sessionId);
693
+ if (!targetServer) {
694
+ console.error(`[SEND_MESSAGE] Unknown server for session ${sessionId}`);
695
+ throw new Error(`Cannot route message: unknown session ${sessionId}`);
696
+ }
697
+ // Get the corresponding WebSocket connection
698
+ const ws = targetServer === 'server1' ? this.ws1 : this.ws2;
699
+ const state = targetServer === 'server1' ? this.state1 : this.state2;
700
+ if (!ws || ws.readyState !== ws_1.default.OPEN) {
701
+ console.error(`[SEND_MESSAGE] ${targetServer} not connected for session ${sessionId}`);
702
+ throw new Error(`${targetServer} is not available`);
703
+ }
704
+ try {
705
+ ws.send(JSON.stringify(message));
706
+ console.log(`[SEND_MESSAGE] Message sent to ${targetServer} for session ${sessionId}, msgType=${message.msgType}`);
707
+ }
708
+ catch (error) {
709
+ console.error(`[SEND_MESSAGE] Failed to send to ${targetServer}:`, error);
710
+ throw error;
711
+ }
712
+ }
538
713
  /**
539
714
  * Send tasks cancel response to specific server
540
715
  */
@@ -591,8 +766,8 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
591
766
  id: message.id,
592
767
  serverId: sourceServer,
593
768
  });
594
- // Remove session mapping
595
- this.sessionServerMap.delete(sessionId);
769
+ // Mark session for cleanup instead of immediate deletion
770
+ this.markSessionForCleanup(sessionId, sourceServer, this.config.sessionCleanupTimeoutMs ?? XiaoYiWebSocketManager.DEFAULT_CLEANUP_TIMEOUT_MS);
596
771
  }
597
772
  /**
598
773
  * Handle clear message (legacy format)
@@ -606,7 +781,8 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
606
781
  id: message.id,
607
782
  serverId: sourceServer,
608
783
  });
609
- this.sessionServerMap.delete(message.sessionId);
784
+ // Mark session for cleanup instead of immediate deletion
785
+ this.markSessionForCleanup(message.sessionId, sourceServer, this.config.sessionCleanupTimeoutMs ?? XiaoYiWebSocketManager.DEFAULT_CLEANUP_TIMEOUT_MS);
610
786
  }
611
787
  /**
612
788
  * Handle tasks/cancel message
@@ -636,7 +812,7 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
636
812
  /**
637
813
  * Convert A2AResponseMessage to JSON-RPC 2.0 format
638
814
  */
639
- convertToJsonRpcFormat(response, taskId, isFinal = true, append = false) {
815
+ convertToJsonRpcFormat(response, taskId, isFinal = true, append = true) {
640
816
  const artifactId = `artifact_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
641
817
  if (response.status === "error" && response.error) {
642
818
  return {
@@ -650,9 +826,11 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
650
826
  }
651
827
  const parts = [];
652
828
  if (response.content.type === "text" && response.content.text) {
829
+ // When isFinal=true, use empty string for text (no content needed for final chunk)
830
+ const textContent = isFinal ? "" : response.content.text;
653
831
  parts.push({
654
832
  kind: "text",
655
- text: response.content.text,
833
+ text: textContent,
656
834
  });
657
835
  }
658
836
  else if (response.content.type === "file") {
@@ -665,10 +843,11 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
665
843
  },
666
844
  });
667
845
  }
846
+ // When isFinal=true, append should be true and text should be empty
668
847
  const artifactEvent = {
669
848
  taskId: taskId,
670
849
  kind: "artifact-update",
671
- append: append,
850
+ append: isFinal ? true : append,
672
851
  lastChunk: isFinal,
673
852
  final: isFinal,
674
853
  artifact: {
@@ -918,6 +1097,86 @@ class XiaoYiWebSocketManager extends events_1.EventEmitter {
918
1097
  removeSession(sessionId) {
919
1098
  this.sessionServerMap.delete(sessionId);
920
1099
  }
1100
+ /**
1101
+ * Mark a session for delayed cleanup
1102
+ * @param sessionId The session ID to mark for cleanup
1103
+ * @param serverId The server ID associated with this session
1104
+ * @param timeoutMs Timeout in milliseconds before forcing cleanup
1105
+ */
1106
+ markSessionForCleanup(sessionId, serverId, timeoutMs) {
1107
+ // Check if already marked
1108
+ const existingState = this.sessionCleanupStateMap.get(sessionId);
1109
+ if (existingState) {
1110
+ // Already pending cleanup, reset timeout
1111
+ if (existingState.cleanupTimeoutId) {
1112
+ clearTimeout(existingState.cleanupTimeoutId);
1113
+ }
1114
+ console.log(`[CLEANUP] Session ${sessionId} already pending cleanup, resetting timeout`);
1115
+ }
1116
+ // Create new cleanup state
1117
+ const newState = {
1118
+ sessionId,
1119
+ serverId,
1120
+ markedForCleanupAt: Date.now(),
1121
+ reason: 'user_cleared',
1122
+ };
1123
+ // Start cleanup timeout
1124
+ const timeoutId = setTimeout(() => {
1125
+ console.log(`[CLEANUP] Timeout reached for session ${sessionId}, forcing cleanup`);
1126
+ this.forceCleanupSession(sessionId);
1127
+ }, timeoutMs);
1128
+ newState.cleanupTimeoutId = timeoutId;
1129
+ this.sessionCleanupStateMap.set(sessionId, newState);
1130
+ console.log(`[CLEANUP] Session ${sessionId} marked for cleanup (timeout: ${timeoutMs}ms)`);
1131
+ }
1132
+ /**
1133
+ * Force cleanup a session immediately
1134
+ * @param sessionId The session ID to cleanup
1135
+ */
1136
+ forceCleanupSession(sessionId) {
1137
+ // Check if already cleaned
1138
+ const state = this.sessionCleanupStateMap.get(sessionId);
1139
+ if (!state) {
1140
+ console.log(`[CLEANUP] Session ${sessionId} already cleaned up, skipping`);
1141
+ return;
1142
+ }
1143
+ // Clear timeout
1144
+ if (state.cleanupTimeoutId) {
1145
+ clearTimeout(state.cleanupTimeoutId);
1146
+ }
1147
+ // Remove from both maps
1148
+ this.sessionServerMap.delete(sessionId);
1149
+ this.sessionCleanupStateMap.delete(sessionId);
1150
+ console.log(`[CLEANUP] Session ${sessionId} cleanup completed`);
1151
+ }
1152
+ /**
1153
+ * Check if a session is pending cleanup
1154
+ * @param sessionId The session ID to check
1155
+ * @returns True if session is pending cleanup
1156
+ */
1157
+ isSessionPendingCleanup(sessionId) {
1158
+ return this.sessionCleanupStateMap.has(sessionId);
1159
+ }
1160
+ /**
1161
+ * Get cleanup state for a session
1162
+ * @param sessionId The session ID to check
1163
+ * @returns Cleanup state if exists, undefined otherwise
1164
+ */
1165
+ getSessionCleanupState(sessionId) {
1166
+ return this.sessionCleanupStateMap.get(sessionId);
1167
+ }
1168
+ /**
1169
+ * Update accumulated text for a pending cleanup session
1170
+ * @param sessionId The session ID
1171
+ * @param text The accumulated text
1172
+ */
1173
+ updateAccumulatedTextForCleanup(sessionId, text) {
1174
+ const state = this.sessionCleanupStateMap.get(sessionId);
1175
+ if (state) {
1176
+ state.accumulatedText = text;
1177
+ }
1178
+ }
921
1179
  }
922
1180
  exports.XiaoYiWebSocketManager = XiaoYiWebSocketManager;
1181
+ XiaoYiWebSocketManager.DEFAULT_CLEANUP_TIMEOUT_MS = 60 * 60 * 1000; // 1 hour
923
1182
  XiaoYiWebSocketManager.STABLE_CONNECTION_THRESHOLD = 10000; // 10 seconds
@@ -0,0 +1,19 @@
1
+ import type { OpenClawConfig, RuntimeEnv } from "openclaw/dist/plugin-sdk/index.js";
2
+ type ClawdbotConfig = OpenClawConfig;
3
+ import type { A2AJsonRpcRequest } from "./types.js";
4
+ /**
5
+ * Parameters for handling an XY message.
6
+ */
7
+ export interface HandleXYMessageParams {
8
+ cfg: ClawdbotConfig;
9
+ runtime: RuntimeEnv;
10
+ message: A2AJsonRpcRequest;
11
+ accountId: string;
12
+ }
13
+ /**
14
+ * Handle an incoming A2A message.
15
+ * This is the main entry point for message processing.
16
+ * Runtime is expected to be validated before calling this function.
17
+ */
18
+ export declare function handleXYMessage(params: HandleXYMessageParams): Promise<void>;
19
+ export {};