@ynhcj/xiaoyi-channel 0.0.112-beta → 0.0.112-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.
Files changed (134) hide show
  1. package/dist/index.d.ts +4 -5
  2. package/dist/index.js +71 -82
  3. package/dist/provider-discovery.d.ts +2 -0
  4. package/dist/provider-discovery.js +4 -0
  5. package/dist/src/bot.d.ts +2 -0
  6. package/dist/src/bot.js +44 -29
  7. package/dist/src/channel.js +2 -21
  8. package/dist/src/client.js +31 -22
  9. package/dist/src/cspl/call-api.js +6 -5
  10. package/dist/src/file-download.js +4 -3
  11. package/dist/src/file-upload.js +19 -18
  12. package/dist/src/formatter.d.ts +2 -0
  13. package/dist/src/formatter.js +25 -35
  14. package/dist/src/heartbeat.js +1 -1
  15. package/dist/src/message-queue.d.ts +17 -0
  16. package/dist/src/message-queue.js +51 -0
  17. package/dist/src/monitor.js +68 -14
  18. package/dist/src/outbound.js +19 -18
  19. package/dist/src/provider.d.ts +1 -1
  20. package/dist/src/provider.js +150 -34
  21. package/dist/src/push.js +16 -15
  22. package/dist/src/reply-dispatcher.js +12 -3
  23. package/dist/src/runtime.d.ts +3 -11
  24. package/dist/src/runtime.js +6 -18
  25. package/dist/src/self-evolution-handler.js +0 -1
  26. package/dist/src/self-evolution-keyword.d.ts +1 -1
  27. package/dist/src/self-evolution-keyword.js +2 -0
  28. package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
  29. package/dist/src/self-evolution-tool-result-nudge.js +96 -0
  30. package/dist/src/skill-retriever/hooks.js +7 -15
  31. package/dist/src/skill-retriever/tool-search.js +22 -8
  32. package/dist/src/skill-retriever/types.d.ts +2 -0
  33. package/dist/src/steer-injector.js +1 -1
  34. package/dist/src/task-manager.d.ts +4 -0
  35. package/dist/src/task-manager.js +12 -1
  36. package/dist/src/tools/calendar-tool.d.ts +2 -1
  37. package/dist/src/tools/calendar-tool.js +112 -116
  38. package/dist/src/tools/call-device-tool.d.ts +2 -1
  39. package/dist/src/tools/call-device-tool.js +126 -103
  40. package/dist/src/tools/call-phone-tool.d.ts +2 -1
  41. package/dist/src/tools/call-phone-tool.js +109 -113
  42. package/dist/src/tools/create-alarm-tool.d.ts +2 -1
  43. package/dist/src/tools/create-alarm-tool.js +227 -231
  44. package/dist/src/tools/create-all-tools.d.ts +16 -0
  45. package/dist/src/tools/create-all-tools.js +50 -0
  46. package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
  47. package/dist/src/tools/delete-alarm-tool.js +131 -135
  48. package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
  49. package/dist/src/tools/get-alarm-tool-schema.js +16 -10
  50. package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
  51. package/dist/src/tools/get-calendar-tool-schema.js +12 -8
  52. package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
  53. package/dist/src/tools/get-collection-tool-schema.js +11 -9
  54. package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
  55. package/dist/src/tools/get-contact-tool-schema.js +16 -10
  56. package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
  57. package/dist/src/tools/get-device-file-tool-schema.js +13 -9
  58. package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
  59. package/dist/src/tools/get-email-tool-schema.js +11 -8
  60. package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
  61. package/dist/src/tools/get-note-tool-schema.js +14 -9
  62. package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
  63. package/dist/src/tools/get-photo-tool-schema.js +12 -9
  64. package/dist/src/tools/image-reading-tool.d.ts +3 -2
  65. package/dist/src/tools/image-reading-tool.js +82 -162
  66. package/dist/src/tools/location-tool.d.ts +2 -1
  67. package/dist/src/tools/location-tool.js +87 -91
  68. package/dist/src/tools/login-token-tool.d.ts +3 -2
  69. package/dist/src/tools/login-token-tool.js +114 -117
  70. package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
  71. package/dist/src/tools/modify-alarm-tool.js +232 -236
  72. package/dist/src/tools/modify-note-tool.d.ts +2 -1
  73. package/dist/src/tools/modify-note-tool.js +104 -108
  74. package/dist/src/tools/note-tool.d.ts +2 -1
  75. package/dist/src/tools/note-tool.js +103 -107
  76. package/dist/src/tools/query-app-message-tool.d.ts +2 -1
  77. package/dist/src/tools/query-app-message-tool.js +108 -111
  78. package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
  79. package/dist/src/tools/query-memory-data-tool.js +109 -112
  80. package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
  81. package/dist/src/tools/query-todo-task-tool.js +103 -106
  82. package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
  83. package/dist/src/tools/save-file-to-phone-tool.js +127 -131
  84. package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
  85. package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
  86. package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -1
  87. package/dist/src/tools/save-self-evolution-skill-tool.js +195 -197
  88. package/dist/src/tools/search-alarm-tool.d.ts +2 -1
  89. package/dist/src/tools/search-alarm-tool.js +171 -175
  90. package/dist/src/tools/search-calendar-tool.d.ts +2 -1
  91. package/dist/src/tools/search-calendar-tool.js +145 -149
  92. package/dist/src/tools/search-contact-tool.d.ts +2 -1
  93. package/dist/src/tools/search-contact-tool.js +98 -102
  94. package/dist/src/tools/search-email-tool.d.ts +2 -1
  95. package/dist/src/tools/search-email-tool.js +107 -111
  96. package/dist/src/tools/search-file-tool.d.ts +2 -1
  97. package/dist/src/tools/search-file-tool.js +99 -103
  98. package/dist/src/tools/search-message-tool.d.ts +2 -1
  99. package/dist/src/tools/search-message-tool.js +100 -104
  100. package/dist/src/tools/search-note-tool.d.ts +2 -1
  101. package/dist/src/tools/search-note-tool.js +95 -99
  102. package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
  103. package/dist/src/tools/search-photo-gallery-tool.js +34 -38
  104. package/dist/src/tools/send-email-tool.d.ts +2 -1
  105. package/dist/src/tools/send-email-tool.js +105 -108
  106. package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
  107. package/dist/src/tools/send-file-to-user-tool.js +158 -155
  108. package/dist/src/tools/send-message-tool.d.ts +2 -1
  109. package/dist/src/tools/send-message-tool.js +119 -123
  110. package/dist/src/tools/session-helper.d.ts +24 -0
  111. package/dist/src/tools/session-helper.js +45 -0
  112. package/dist/src/tools/session-manager.d.ts +29 -6
  113. package/dist/src/tools/session-manager.js +146 -19
  114. package/dist/src/tools/upload-file-tool.d.ts +2 -1
  115. package/dist/src/tools/upload-file-tool.js +78 -82
  116. package/dist/src/tools/upload-photo-tool.d.ts +2 -1
  117. package/dist/src/tools/upload-photo-tool.js +69 -73
  118. package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
  119. package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
  120. package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
  121. package/dist/src/tools/xiaoyi-collection-tool.js +111 -115
  122. package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
  123. package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
  124. package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
  125. package/dist/src/tools/xiaoyi-gui-tool.js +87 -88
  126. package/dist/src/utils/logger.js +20 -18
  127. package/dist/src/utils/self-evolution-manager.d.ts +5 -0
  128. package/dist/src/utils/self-evolution-manager.js +45 -23
  129. package/dist/src/websocket.d.ts +3 -0
  130. package/dist/src/websocket.js +73 -29
  131. package/dist/src/xy-session-store.d.ts +79 -0
  132. package/dist/src/xy-session-store.js +153 -0
  133. package/openclaw.plugin.json +22 -0
  134. package/package.json +2 -2
@@ -3,13 +3,14 @@
3
3
  import fetch from "node-fetch";
4
4
  import fs from "fs/promises";
5
5
  import os from "os";
6
+ import { logger } from "./utils/logger.js";
6
7
  import path from "path";
7
8
  import { calculateSHA256 } from "./utils/crypto.js";
8
9
  function isRemoteUrl(filePath) {
9
10
  return filePath.startsWith("http://") || filePath.startsWith("https://");
10
11
  }
11
12
  async function downloadToTempFile(url) {
12
- console.log(`[XY File Upload] Downloading remote file: ${url}`);
13
+ logger.log(`[XY File Upload] Downloading remote file: ${url}`);
13
14
  const response = await fetch(url);
14
15
  if (!response.ok) {
15
16
  throw new Error(`Failed to download remote file: HTTP ${response.status}`);
@@ -18,7 +19,7 @@ async function downloadToTempFile(url) {
18
19
  const urlFileName = path.basename(new URL(url).pathname) || "download";
19
20
  const tempPath = path.join(os.tmpdir(), `xy-upload-${Date.now()}-${urlFileName}`);
20
21
  await fs.writeFile(tempPath, buffer);
21
- console.log(`[XY File Upload] Downloaded to temp file: ${tempPath}`);
22
+ logger.log(`[XY File Upload] Downloaded to temp file: ${tempPath}`);
22
23
  return tempPath;
23
24
  }
24
25
  /**
@@ -39,7 +40,7 @@ export class XYFileUploadService {
39
40
  * Returns the objectId (as fileId) for use in A2A messages.
40
41
  */
41
42
  async uploadFile(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
42
- console.log(`[XY File Upload] Starting file upload: ${filePath}`);
43
+ logger.log(`[XY File Upload] Starting file upload: ${filePath}`);
43
44
  let localFilePath = filePath;
44
45
  let isTempFile = false;
45
46
  try {
@@ -54,7 +55,7 @@ export class XYFileUploadService {
54
55
  const fileSha256 = calculateSHA256(fileBuffer);
55
56
  const fileSize = fileBuffer.length;
56
57
  // Phase 1: Prepare
57
- console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
58
+ logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
58
59
  const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
59
60
  method: "POST",
60
61
  headers: {
@@ -84,7 +85,7 @@ export class XYFileUploadService {
84
85
  }
85
86
  const { objectId, draftId, uploadInfos } = prepareData;
86
87
  // Phase 2: Upload
87
- console.log(`[XY File Upload] Phase 2: Upload file data`);
88
+ logger.log(`[XY File Upload] Phase 2: Upload file data`);
88
89
  const uploadInfo = uploadInfos[0]; // Single-part upload
89
90
  const uploadResp = await fetch(uploadInfo.url, {
90
91
  method: uploadInfo.method,
@@ -95,9 +96,9 @@ export class XYFileUploadService {
95
96
  const uploadErrorText = await uploadResp.text();
96
97
  throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
97
98
  }
98
- console.log(`[XY File Upload] Upload complete`);
99
+ logger.log(`[XY File Upload] Upload complete`);
99
100
  // Phase 3: Complete
100
- console.log(`[XY File Upload] Phase 3: Complete upload`);
101
+ logger.log(`[XY File Upload] Phase 3: Complete upload`);
101
102
  const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/complete`, {
102
103
  method: "POST",
103
104
  headers: {
@@ -115,11 +116,11 @@ export class XYFileUploadService {
115
116
  throw new Error(`Complete failed: HTTP ${completeResp.status}`);
116
117
  }
117
118
  const completeData = await completeResp.json();
118
- console.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
119
+ logger.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
119
120
  return objectId;
120
121
  }
121
122
  catch (error) {
122
- console.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
123
+ logger.error(`[XY File Upload] File upload failed for ${filePath}:`, error);
123
124
  throw error;
124
125
  }
125
126
  finally {
@@ -150,7 +151,7 @@ export class XYFileUploadService {
150
151
  const fileSha256 = calculateSHA256(fileBuffer);
151
152
  const fileSize = fileBuffer.length;
152
153
  // Phase 1: Prepare
153
- console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
154
+ logger.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
154
155
  const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
155
156
  method: "POST",
156
157
  headers: {
@@ -179,23 +180,23 @@ export class XYFileUploadService {
179
180
  throw new Error(`Prepare failed: ${prepareData.desc}`);
180
181
  }
181
182
  const { objectId, draftId, uploadInfos } = prepareData;
182
- console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
183
+ logger.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
183
184
  // Phase 2: Upload
184
- console.log(`[XY File Upload] Phase 2: Upload file data`);
185
+ logger.log(`[XY File Upload] Phase 2: Upload file data`);
185
186
  const uploadInfo = uploadInfos[0]; // Single-part upload
186
187
  const uploadResp = await fetch(uploadInfo.url, {
187
188
  method: uploadInfo.method,
188
189
  headers: uploadInfo.headers,
189
190
  body: fileBuffer,
190
191
  });
191
- console.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
192
+ logger.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
192
193
  if (!uploadResp.ok) {
193
194
  const uploadErrorText = await uploadResp.text();
194
195
  throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
195
196
  }
196
- console.log(`[XY File Upload] Upload complete`);
197
+ logger.log(`[XY File Upload] Upload complete`);
197
198
  // Phase 3: CompleteAndQuery - get file URL
198
- console.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
199
+ logger.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
199
200
  const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
200
201
  method: "POST",
201
202
  headers: {
@@ -218,11 +219,11 @@ export class XYFileUploadService {
218
219
  if (!fileUrl) {
219
220
  throw new Error("No file URL returned from completeAndQuery");
220
221
  }
221
- console.log(`[XY File Upload] File upload successful`);
222
+ logger.log(`[XY File Upload] File upload successful`);
222
223
  return fileUrl;
223
224
  }
224
225
  catch (error) {
225
- console.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
226
+ logger.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
226
227
  throw error;
227
228
  }
228
229
  finally {
@@ -249,7 +250,7 @@ export class XYFileUploadService {
249
250
  });
250
251
  }
251
252
  catch (error) {
252
- console.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
253
+ logger.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
253
254
  // Continue with other files
254
255
  }
255
256
  }
@@ -17,6 +17,7 @@ export interface SendA2AResponseParams {
17
17
  }>;
18
18
  errorCode?: number | string;
19
19
  errorMessage?: string;
20
+ runtime?: any;
20
21
  }
21
22
  /**
22
23
  * Send an A2A artifact update response.
@@ -49,6 +50,7 @@ export interface SendStatusUpdateParams {
49
50
  messageId: string;
50
51
  text: string;
51
52
  state: "submitted" | "working" | "input-required" | "completed" | "canceled" | "failed" | "unknown";
53
+ runtime?: any;
52
54
  }
53
55
  /**
54
56
  * Send an A2A task status update.
@@ -1,15 +1,14 @@
1
1
  // OpenClaw → A2A format conversion
2
2
  import { v4 as uuidv4 } from "uuid";
3
3
  import { getXYWebSocketManager } from "./client.js";
4
- import { getXYRuntime } from "./runtime.js";
4
+ import { logger } from "./utils/logger.js";
5
+ import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
5
6
  /**
6
7
  * Send an A2A artifact update response.
7
8
  */
8
9
  export async function sendA2AResponse(params) {
9
- const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
10
- const runtime = getXYRuntime();
10
+ const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage, runtime } = params;
11
11
  const log = runtime?.log ?? console.log;
12
- const errorFn = runtime?.error ?? console.error;
13
12
  // Build artifact update event
14
13
  const artifact = {
15
14
  taskId,
@@ -75,9 +74,6 @@ export async function sendA2AResponse(params) {
75
74
  */
76
75
  export async function sendReasoningTextUpdate(params) {
77
76
  const { config, sessionId, taskId, messageId, text, append = true } = params;
78
- const runtime = getXYRuntime();
79
- const log = runtime?.log ?? console.log;
80
- const error = runtime?.error ?? console.error;
81
77
  const artifact = {
82
78
  taskId,
83
79
  kind: "artifact-update",
@@ -114,13 +110,15 @@ export async function sendReasoningTextUpdate(params) {
114
110
  * Follows A2A protocol standard format with nested status object.
115
111
  */
116
112
  export async function sendStatusUpdate(params) {
117
- const { config, sessionId, taskId, messageId, text, state } = params;
118
- const runtime = getXYRuntime();
113
+ const { config, sessionId, taskId, messageId, text, state, runtime } = params;
119
114
  const log = runtime?.log ?? console.log;
120
- const error = runtime?.error ?? console.error;
115
+ // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
116
+ // fall back to closure-captured values
117
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
118
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
121
119
  // Build status update event following A2A protocol standard
122
120
  const statusUpdate = {
123
- taskId,
121
+ taskId: currentTaskId,
124
122
  kind: "status-update",
125
123
  final: false, // Status updates should not end the stream
126
124
  status: {
@@ -139,7 +137,7 @@ export async function sendStatusUpdate(params) {
139
137
  // Build JSON-RPC response
140
138
  const jsonRpcResponse = {
141
139
  jsonrpc: "2.0",
142
- id: messageId,
140
+ id: currentMessageId,
143
141
  result: statusUpdate,
144
142
  };
145
143
  // Send via WebSocket
@@ -148,12 +146,12 @@ export async function sendStatusUpdate(params) {
148
146
  msgType: "agent_response",
149
147
  agentId: config.agentId,
150
148
  sessionId,
151
- taskId,
149
+ taskId: currentTaskId,
152
150
  msgDetail: JSON.stringify(jsonRpcResponse),
153
151
  };
154
152
  // 📋 Log complete response body
155
153
  log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
156
- log(`[A2A_STATUS] - taskId: ${taskId}`);
154
+ log(`[A2A_STATUS] - taskId: ${currentTaskId}`);
157
155
  log(`[A2A_STATUS] - text: "${text}"`);
158
156
  await wsManager.sendMessage(sessionId, outboundMessage);
159
157
  }
@@ -162,13 +160,14 @@ export async function sendStatusUpdate(params) {
162
160
  */
163
161
  export async function sendCommand(params) {
164
162
  const { config, sessionId, taskId, messageId, command } = params;
165
- const runtime = getXYRuntime();
166
- const log = runtime?.log ?? console.log;
167
- const error = runtime?.error ?? console.error;
163
+ // Dynamic lookup: use latest taskId/messageId from task-manager (handles steer/interrupt),
164
+ // fall back to closure-captured values
165
+ const currentTaskId = getCurrentTaskId(sessionId) ?? taskId;
166
+ const currentMessageId = getCurrentMessageId(sessionId) ?? messageId;
168
167
  // Build artifact update with command as data
169
168
  // Wrap command in commands array as per protocol requirement
170
169
  const artifact = {
171
- taskId,
170
+ taskId: currentTaskId,
172
171
  kind: "artifact-update",
173
172
  append: false,
174
173
  lastChunk: true,
@@ -188,7 +187,7 @@ export async function sendCommand(params) {
188
187
  // Build JSON-RPC response
189
188
  const jsonRpcResponse = {
190
189
  jsonrpc: "2.0",
191
- id: messageId,
190
+ id: currentMessageId,
192
191
  result: artifact,
193
192
  };
194
193
  // Send via WebSocket
@@ -197,22 +196,19 @@ export async function sendCommand(params) {
197
196
  msgType: "agent_response",
198
197
  agentId: config.agentId,
199
198
  sessionId,
200
- taskId,
199
+ taskId: currentTaskId,
201
200
  msgDetail: JSON.stringify(jsonRpcResponse),
202
201
  };
203
202
  // 📋 Log complete response body
204
- log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
203
+ logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${currentTaskId}`);
205
204
  await wsManager.sendMessage(sessionId, outboundMessage);
206
- log(`[A2A_COMMAND] ✅ Command sent successfully`);
205
+ logger.log(`[A2A_COMMAND] ✅ Command sent successfully`);
207
206
  }
208
207
  /**
209
208
  * Send a clearContext response.
210
209
  */
211
210
  export async function sendClearContextResponse(params) {
212
211
  const { config, sessionId, messageId } = params;
213
- const runtime = getXYRuntime();
214
- const log = runtime?.log ?? console.log;
215
- const error = runtime?.error ?? console.error;
216
212
  // Build JSON-RPC response for clearContext
217
213
  const jsonRpcResponse = {
218
214
  jsonrpc: "2.0",
@@ -238,16 +234,13 @@ export async function sendClearContextResponse(params) {
238
234
  msgDetail: JSON.stringify(jsonRpcResponse),
239
235
  };
240
236
  await wsManager.sendMessage(sessionId, outboundMessage);
241
- log(`Sent clearContext response: sessionId=${sessionId}`);
237
+ logger.log(`Sent clearContext response: sessionId=${sessionId}`);
242
238
  }
243
239
  /**
244
240
  * Send a tasks/cancel response.
245
241
  */
246
242
  export async function sendTasksCancelResponse(params) {
247
243
  const { config, sessionId, taskId, messageId } = params;
248
- const runtime = getXYRuntime();
249
- const log = runtime?.log ?? console.log;
250
- const error = runtime?.error ?? console.error;
251
244
  // Build JSON-RPC response for tasks/cancel
252
245
  // Note: Using any to bypass type check as the response format differs from standard A2A types
253
246
  const jsonRpcResponse = {
@@ -274,16 +267,13 @@ export async function sendTasksCancelResponse(params) {
274
267
  msgDetail: JSON.stringify(jsonRpcResponse),
275
268
  };
276
269
  await wsManager.sendMessage(sessionId, outboundMessage);
277
- log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
270
+ logger.log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
278
271
  }
279
272
  /**
280
273
  * Send a Trigger response with pushData content.
281
274
  */
282
275
  export async function sendTriggerResponse(params) {
283
276
  const { config, sessionId, taskId, messageId, content } = params;
284
- const runtime = getXYRuntime();
285
- const log = runtime?.log ?? console.log;
286
- const error = runtime?.error ?? console.error;
287
277
  // Build JSON-RPC response for Trigger
288
278
  const jsonRpcResponse = {
289
279
  jsonrpc: "2.0",
@@ -318,7 +308,7 @@ export async function sendTriggerResponse(params) {
318
308
  taskId,
319
309
  msgDetail: JSON.stringify(jsonRpcResponse),
320
310
  };
321
- log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
311
+ logger.log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
322
312
  await wsManager.sendMessage(sessionId, outboundMessage);
323
- log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
313
+ logger.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
324
314
  }
@@ -67,7 +67,7 @@ export class HeartbeatManager {
67
67
  */
68
68
  sendHeartbeat() {
69
69
  if (this.ws.readyState !== WebSocket.OPEN) {
70
- console.warn(`Cannot send heartbeat for ${this.serverName}: WebSocket not open`);
70
+ this.log(`Cannot send heartbeat for ${this.serverName}: WebSocket not open`);
71
71
  return;
72
72
  }
73
73
  try {
@@ -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
+ }
@@ -1,12 +1,14 @@
1
1
  import { resolveXYConfig } from "./config.js";
2
- import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, removeXYWebSocketManager } from "./client.js";
2
+ import { getXYWebSocketManager, setClientRuntime, 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";
9
10
  import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
11
+ import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
10
12
  /**
11
13
  * Per-session serial queue that ensures messages from the same session are processed
12
14
  * in arrival order while allowing different sessions to run concurrently.
@@ -49,8 +51,10 @@ export async function monitorXYProvider(opts = {}) {
49
51
  opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
50
52
  }
51
53
  : undefined;
54
+ // ✅ Set runtime for WebSocket manager logging before creating/getting manager
55
+ setClientRuntime(runtime);
52
56
  // 🔍 Diagnose WebSocket managers before gateway start
53
- console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
57
+ log("🔍 [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
54
58
  diagnoseAllManagers();
55
59
  // Get WebSocket manager (cached)
56
60
  const wsManager = getXYWebSocketManager(account);
@@ -64,6 +68,12 @@ export async function monitorXYProvider(opts = {}) {
64
68
  const activeMessages = new Set();
65
69
  // Create session queue for ordered message processing
66
70
  const enqueue = createSessionQueue();
71
+ // Global gate that serializes dispatch initialization across sessions.
72
+ // When a new session starts dispatching, it acquires this gate and holds it
73
+ // until agent setup (agentTools + wrapStreamFn) is complete, then releases it.
74
+ // This prevents lastRegisteredKey races when multiple sessions initialize
75
+ // concurrently.
76
+ let globalDispatchInitGate = Promise.resolve();
67
77
  // Health check interval
68
78
  let healthCheckInterval = null;
69
79
  return new Promise((resolve, reject) => {
@@ -79,6 +89,11 @@ export async function monitorXYProvider(opts = {}) {
79
89
  }
80
90
  activeMessages.add(messageKey);
81
91
  const task = async () => {
92
+ // Wait for the previous session's init to complete (global gate),
93
+ // then acquire the gate for this session's init.
94
+ await globalDispatchInitGate;
95
+ let releaseGate;
96
+ globalDispatchInitGate = new Promise((r) => { releaseGate = r; });
82
97
  try {
83
98
  await handleXYMessage({
84
99
  cfg,
@@ -86,10 +101,12 @@ export async function monitorXYProvider(opts = {}) {
86
101
  message,
87
102
  accountId, // ✅ Pass accountId ("default")
88
103
  webSocketSessionId: sessionId, // ✅ 传递 WebSocket 层级的 sessionId
104
+ onInitComplete: () => releaseGate(),
89
105
  });
90
106
  }
91
107
  catch (err) {
92
108
  // ✅ Only log error, don't re-throw to prevent gateway restart
109
+ releaseGate();
93
110
  error(`XY gateway: error handling message from ${serverId}: ${String(err)}`);
94
111
  }
95
112
  finally {
@@ -139,7 +156,7 @@ export async function monitorXYProvider(opts = {}) {
139
156
  opts.setStatus?.({ connected: true });
140
157
  };
141
158
  const disconnectedHandler = (serverId) => {
142
- console.warn(`XY gateway: ${serverId} disconnected`);
159
+ log(`XY gateway: ${serverId} disconnected`);
143
160
  loggedServers.delete(serverId);
144
161
  // ✅ Report disconnection status (only if all servers disconnected)
145
162
  if (loggedServers.size === 0) {
@@ -164,7 +181,7 @@ export async function monitorXYProvider(opts = {}) {
164
181
  };
165
182
  const selfEvolutionStateGetHandler = (context) => {
166
183
  log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
167
- handleSelfEvolutionStateGetEvent(context, cfg, runtime, wsManager).catch((err) => {
184
+ handleSelfEvolutionStateGetEvent(context, account, runtime, wsManager).catch((err) => {
168
185
  error(`[MONITOR] Failed to handle self-evolution-state-get-event:`, err);
169
186
  });
170
187
  };
@@ -175,13 +192,13 @@ export async function monitorXYProvider(opts = {}) {
175
192
  const cleanup = () => {
176
193
  log("XY gateway: cleaning up...");
177
194
  // 🔍 Diagnose before cleanup
178
- console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
195
+ log("🔍 [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
179
196
  diagnoseAllManagers();
180
197
  // Stop health check interval
181
198
  if (healthCheckInterval) {
182
199
  clearInterval(healthCheckInterval);
183
200
  healthCheckInterval = null;
184
- console.log("⏸️ Stopped periodic health check");
201
+ log("⏸️ Stopped periodic health check");
185
202
  }
186
203
  // Remove event handlers to prevent duplicate calls on gateway restart
187
204
  wsManager.off("message", messageHandler);
@@ -197,15 +214,46 @@ export async function monitorXYProvider(opts = {}) {
197
214
  wsManager.disconnect();
198
215
  // ✅ Remove manager from cache to prevent reusing dirty state
199
216
  removeXYWebSocketManager(account);
217
+ // Clean up all active sessions
218
+ cleanupAllSessions();
200
219
  loggedServers.clear();
201
220
  activeMessages.clear();
202
- log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages`);
221
+ log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages and sessions`);
203
222
  // 🔍 Diagnose after cleanup
204
- console.log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
223
+ log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
205
224
  diagnoseAllManagers();
206
225
  };
207
- const handleAbort = () => {
208
- log("XY gateway: abort signal received, stopping");
226
+ const handleAbort = async () => {
227
+ log("XY gateway: abort signal received, sending notifications before stopping");
228
+ // 📤 Send restart notification to all active sessions before disconnecting
229
+ try {
230
+ const activeBindings = getAllActiveTaskBindings();
231
+ if (activeBindings.length > 0) {
232
+ const config = resolveXYConfig(cfg);
233
+ const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
234
+ log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
235
+ const sendPromises = activeBindings.map(binding => sendA2AResponse({
236
+ config,
237
+ sessionId: binding.sessionId,
238
+ taskId: binding.currentTaskId,
239
+ messageId: binding.currentMessageId,
240
+ text: notificationText,
241
+ append: false,
242
+ final: true,
243
+ runtime,
244
+ }).catch(err => {
245
+ error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
246
+ }));
247
+ await Promise.all(sendPromises);
248
+ log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.length} session(s)`);
249
+ }
250
+ else {
251
+ log(`[MONITOR] No active sessions, skipping restart notifications`);
252
+ }
253
+ }
254
+ catch (err) {
255
+ error(`[MONITOR] Error sending restart notifications: ${String(err)}`);
256
+ }
209
257
  cleanup();
210
258
  log("XY gateway stopped");
211
259
  resolve();
@@ -226,14 +274,20 @@ export async function monitorXYProvider(opts = {}) {
226
274
  wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
227
275
  wsManager.on("login-token-event", loginTokenEventHandler);
228
276
  // Start periodic health check (every 6 hours)
229
- console.log("🏥 Starting periodic health check (every 6 hours)...");
277
+ log("🏥 Starting periodic health check (every 6 hours)...");
230
278
  healthCheckInterval = setInterval(() => {
231
- console.log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
279
+ log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
232
280
  diagnoseAllManagers();
233
281
  // Auto-cleanup orphan connections
234
282
  const cleaned = cleanupOrphanConnections();
235
283
  if (cleaned > 0) {
236
- console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
284
+ log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
285
+ }
286
+ // Cleanup stale sessions (older than 10min TTL)
287
+ const cleanedSessions = cleanupStaleSessions();
288
+ const remainingSessions = getActiveSessionCount();
289
+ if (cleanedSessions > 0 || remainingSessions > 0) {
290
+ log(`🧹 [HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
237
291
  }
238
292
  // Cleanup stale temp files (older than 24 hours)
239
293
  void cleanupStaleTempFiles();