@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.
- package/dist/index.d.ts +4 -5
- package/dist/index.js +71 -82
- package/dist/provider-discovery.d.ts +2 -0
- package/dist/provider-discovery.js +4 -0
- package/dist/src/bot.d.ts +2 -0
- package/dist/src/bot.js +44 -29
- package/dist/src/channel.js +2 -21
- package/dist/src/client.js +31 -22
- package/dist/src/cspl/call-api.js +6 -5
- package/dist/src/file-download.js +4 -3
- package/dist/src/file-upload.js +19 -18
- package/dist/src/formatter.d.ts +2 -0
- package/dist/src/formatter.js +25 -35
- package/dist/src/heartbeat.js +1 -1
- package/dist/src/message-queue.d.ts +17 -0
- package/dist/src/message-queue.js +51 -0
- package/dist/src/monitor.js +68 -14
- package/dist/src/outbound.js +19 -18
- package/dist/src/provider.d.ts +1 -1
- package/dist/src/provider.js +150 -34
- package/dist/src/push.js +16 -15
- package/dist/src/reply-dispatcher.js +12 -3
- package/dist/src/runtime.d.ts +3 -11
- package/dist/src/runtime.js +6 -18
- package/dist/src/self-evolution-handler.js +0 -1
- package/dist/src/self-evolution-keyword.d.ts +1 -1
- package/dist/src/self-evolution-keyword.js +2 -0
- package/dist/src/self-evolution-tool-result-nudge.d.ts +3 -0
- package/dist/src/self-evolution-tool-result-nudge.js +96 -0
- package/dist/src/skill-retriever/hooks.js +7 -15
- package/dist/src/skill-retriever/tool-search.js +22 -8
- package/dist/src/skill-retriever/types.d.ts +2 -0
- package/dist/src/steer-injector.js +1 -1
- package/dist/src/task-manager.d.ts +4 -0
- package/dist/src/task-manager.js +12 -1
- package/dist/src/tools/calendar-tool.d.ts +2 -1
- package/dist/src/tools/calendar-tool.js +112 -116
- package/dist/src/tools/call-device-tool.d.ts +2 -1
- package/dist/src/tools/call-device-tool.js +126 -103
- package/dist/src/tools/call-phone-tool.d.ts +2 -1
- package/dist/src/tools/call-phone-tool.js +109 -113
- package/dist/src/tools/create-alarm-tool.d.ts +2 -1
- package/dist/src/tools/create-alarm-tool.js +227 -231
- package/dist/src/tools/create-all-tools.d.ts +16 -0
- package/dist/src/tools/create-all-tools.js +50 -0
- package/dist/src/tools/delete-alarm-tool.d.ts +2 -1
- package/dist/src/tools/delete-alarm-tool.js +131 -135
- package/dist/src/tools/get-alarm-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-alarm-tool-schema.js +16 -10
- package/dist/src/tools/get-calendar-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-calendar-tool-schema.js +12 -8
- package/dist/src/tools/get-collection-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-collection-tool-schema.js +11 -9
- package/dist/src/tools/get-contact-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-contact-tool-schema.js +16 -10
- package/dist/src/tools/get-device-file-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-device-file-tool-schema.js +13 -9
- package/dist/src/tools/get-email-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-email-tool-schema.js +11 -8
- package/dist/src/tools/get-note-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-note-tool-schema.js +14 -9
- package/dist/src/tools/get-photo-tool-schema.d.ts +2 -1
- package/dist/src/tools/get-photo-tool-schema.js +12 -9
- package/dist/src/tools/image-reading-tool.d.ts +3 -2
- package/dist/src/tools/image-reading-tool.js +82 -162
- package/dist/src/tools/location-tool.d.ts +2 -1
- package/dist/src/tools/location-tool.js +87 -91
- package/dist/src/tools/login-token-tool.d.ts +3 -2
- package/dist/src/tools/login-token-tool.js +114 -117
- package/dist/src/tools/modify-alarm-tool.d.ts +2 -1
- package/dist/src/tools/modify-alarm-tool.js +232 -236
- package/dist/src/tools/modify-note-tool.d.ts +2 -1
- package/dist/src/tools/modify-note-tool.js +104 -108
- package/dist/src/tools/note-tool.d.ts +2 -1
- package/dist/src/tools/note-tool.js +103 -107
- package/dist/src/tools/query-app-message-tool.d.ts +2 -1
- package/dist/src/tools/query-app-message-tool.js +108 -111
- package/dist/src/tools/query-memory-data-tool.d.ts +2 -1
- package/dist/src/tools/query-memory-data-tool.js +109 -112
- package/dist/src/tools/query-todo-task-tool.d.ts +2 -1
- package/dist/src/tools/query-todo-task-tool.js +103 -106
- package/dist/src/tools/save-file-to-phone-tool.d.ts +2 -1
- package/dist/src/tools/save-file-to-phone-tool.js +127 -131
- package/dist/src/tools/save-media-to-gallery-tool.d.ts +2 -1
- package/dist/src/tools/save-media-to-gallery-tool.js +134 -138
- package/dist/src/tools/save-self-evolution-skill-tool.d.ts +2 -1
- package/dist/src/tools/save-self-evolution-skill-tool.js +195 -197
- package/dist/src/tools/search-alarm-tool.d.ts +2 -1
- package/dist/src/tools/search-alarm-tool.js +171 -175
- package/dist/src/tools/search-calendar-tool.d.ts +2 -1
- package/dist/src/tools/search-calendar-tool.js +145 -149
- package/dist/src/tools/search-contact-tool.d.ts +2 -1
- package/dist/src/tools/search-contact-tool.js +98 -102
- package/dist/src/tools/search-email-tool.d.ts +2 -1
- package/dist/src/tools/search-email-tool.js +107 -111
- package/dist/src/tools/search-file-tool.d.ts +2 -1
- package/dist/src/tools/search-file-tool.js +99 -103
- package/dist/src/tools/search-message-tool.d.ts +2 -1
- package/dist/src/tools/search-message-tool.js +100 -104
- package/dist/src/tools/search-note-tool.d.ts +2 -1
- package/dist/src/tools/search-note-tool.js +95 -99
- package/dist/src/tools/search-photo-gallery-tool.d.ts +2 -1
- package/dist/src/tools/search-photo-gallery-tool.js +34 -38
- package/dist/src/tools/send-email-tool.d.ts +2 -1
- package/dist/src/tools/send-email-tool.js +105 -108
- package/dist/src/tools/send-file-to-user-tool.d.ts +2 -1
- package/dist/src/tools/send-file-to-user-tool.js +158 -155
- package/dist/src/tools/send-message-tool.d.ts +2 -1
- package/dist/src/tools/send-message-tool.js +119 -123
- package/dist/src/tools/session-helper.d.ts +24 -0
- package/dist/src/tools/session-helper.js +45 -0
- package/dist/src/tools/session-manager.d.ts +29 -6
- package/dist/src/tools/session-manager.js +146 -19
- package/dist/src/tools/upload-file-tool.d.ts +2 -1
- package/dist/src/tools/upload-file-tool.js +78 -82
- package/dist/src/tools/upload-photo-tool.d.ts +2 -1
- package/dist/src/tools/upload-photo-tool.js +69 -73
- package/dist/src/tools/xiaoyi-add-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-add-collection-tool.js +143 -147
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-collection-tool.js +111 -115
- package/dist/src/tools/xiaoyi-delete-collection-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-delete-collection-tool.js +124 -128
- package/dist/src/tools/xiaoyi-gui-tool.d.ts +2 -1
- package/dist/src/tools/xiaoyi-gui-tool.js +87 -88
- package/dist/src/utils/logger.js +20 -18
- package/dist/src/utils/self-evolution-manager.d.ts +5 -0
- package/dist/src/utils/self-evolution-manager.js +45 -23
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +73 -29
- package/dist/src/xy-session-store.d.ts +79 -0
- package/dist/src/xy-session-store.js +153 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +2 -2
package/dist/src/file-upload.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
logger.log(`[XY File Upload] Upload complete`);
|
|
99
100
|
// Phase 3: Complete
|
|
100
|
-
|
|
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
|
-
|
|
119
|
+
logger.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
|
|
119
120
|
return objectId;
|
|
120
121
|
}
|
|
121
122
|
catch (error) {
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
+
logger.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
|
|
183
184
|
// Phase 2: Upload
|
|
184
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
+
logger.log(`[XY File Upload] Upload complete`);
|
|
197
198
|
// Phase 3: CompleteAndQuery - get file URL
|
|
198
|
-
|
|
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
|
-
|
|
222
|
+
logger.log(`[XY File Upload] File upload successful`);
|
|
222
223
|
return fileUrl;
|
|
223
224
|
}
|
|
224
225
|
catch (error) {
|
|
225
|
-
|
|
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
|
-
|
|
253
|
+
logger.error(`[XY File Upload] Failed to upload ${filePath}, skipping:`, error);
|
|
253
254
|
// Continue with other files
|
|
254
255
|
}
|
|
255
256
|
}
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -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.
|
package/dist/src/formatter.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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:
|
|
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: ${
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
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:
|
|
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: ${
|
|
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
|
}
|
package/dist/src/heartbeat.js
CHANGED
|
@@ -67,7 +67,7 @@ export class HeartbeatManager {
|
|
|
67
67
|
*/
|
|
68
68
|
sendHeartbeat() {
|
|
69
69
|
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
70
|
-
|
|
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
|
+
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
+
log("🏥 Starting periodic health check (every 6 hours)...");
|
|
230
278
|
healthCheckInterval = setInterval(() => {
|
|
231
|
-
|
|
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
|
-
|
|
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();
|