@ynhcj/xiaoyi-channel 1.1.8 → 1.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/bot.js +51 -8
- package/dist/src/channel.js +7 -5
- package/dist/src/client.js +11 -24
- package/dist/src/config.js +2 -2
- package/dist/src/file-upload.d.ts +5 -0
- package/dist/src/file-upload.js +92 -0
- package/dist/src/formatter.d.ts +14 -0
- package/dist/src/formatter.js +46 -2
- package/dist/src/monitor.js +12 -0
- package/dist/src/outbound.js +122 -88
- package/dist/src/parser.d.ts +7 -0
- package/dist/src/parser.js +22 -0
- package/dist/src/push.d.ts +8 -1
- package/dist/src/push.js +30 -22
- package/dist/src/reply-dispatcher.js +0 -2
- package/dist/src/tools/create-alarm-tool.js +2 -28
- package/dist/src/tools/delete-alarm-tool.js +2 -24
- package/dist/src/tools/image-reading-tool.d.ts +5 -0
- package/dist/src/tools/image-reading-tool.js +353 -0
- package/dist/src/tools/location-tool.js +6 -9
- package/dist/src/tools/modify-alarm-tool.js +2 -30
- package/dist/src/tools/modify-note-tool.js +2 -7
- package/dist/src/tools/note-tool.js +3 -13
- package/dist/src/tools/search-alarm-tool.js +9 -57
- package/dist/src/tools/search-calendar-tool.js +3 -43
- package/dist/src/tools/search-contact-tool.js +2 -25
- package/dist/src/tools/search-file-tool.js +5 -29
- package/dist/src/tools/search-message-tool.js +3 -27
- package/dist/src/tools/search-note-tool.js +27 -20
- package/dist/src/tools/search-photo-gallery-tool.js +39 -31
- package/dist/src/tools/send-message-tool.js +6 -19
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +118 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +190 -0
- package/dist/src/trigger-handler.d.ts +22 -0
- package/dist/src/trigger-handler.js +59 -0
- package/dist/src/types.d.ts +1 -8
- package/dist/src/types.js +4 -0
- package/dist/src/utils/pushdata-manager.d.ts +28 -0
- package/dist/src/utils/pushdata-manager.js +171 -0
- package/dist/src/utils/pushid-manager.d.ts +12 -0
- package/dist/src/utils/pushid-manager.js +105 -0
- package/dist/src/websocket.d.ts +25 -31
- package/dist/src/websocket.js +218 -270
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { getXYRuntime } from "./runtime.js";
|
|
2
2
|
import { createXYReplyDispatcher } from "./reply-dispatcher.js";
|
|
3
|
-
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId } from "./parser.js";
|
|
3
|
+
import { parseA2AMessage, extractTextFromParts, extractFileParts, extractPushId, extractTriggerData } from "./parser.js";
|
|
4
4
|
import { downloadFilesFromParts } from "./file-download.js";
|
|
5
5
|
import { resolveXYConfig } from "./config.js";
|
|
6
|
-
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse } from "./formatter.js";
|
|
6
|
+
import { sendStatusUpdate, sendClearContextResponse, sendTasksCancelResponse, sendA2AResponse } from "./formatter.js";
|
|
7
7
|
import { registerSession, unregisterSession, runWithSessionContext } from "./tools/session-manager.js";
|
|
8
8
|
import { configManager } from "./utils/config-manager.js";
|
|
9
|
+
import { addPushId } from "./utils/pushid-manager.js";
|
|
10
|
+
import { getPushDataById } from "./utils/pushdata-manager.js";
|
|
9
11
|
import { registerTaskId, decrementTaskIdRef, lockTaskId, unlockTaskId, hasActiveTask, } from "./task-manager.js";
|
|
10
12
|
/**
|
|
11
13
|
* Handle an incoming A2A message.
|
|
@@ -57,6 +59,44 @@ export async function handleXYMessage(params) {
|
|
|
57
59
|
}
|
|
58
60
|
// Parse the A2A message (for regular messages)
|
|
59
61
|
const parsed = parseA2AMessage(message);
|
|
62
|
+
// ========== 检测 Trigger 消息 ==========
|
|
63
|
+
// 如果消息中包含 Trigger 事件数据,直接返回 pushData 内容,不走正常流程
|
|
64
|
+
const triggerData = extractTriggerData(parsed.parts);
|
|
65
|
+
if (triggerData) {
|
|
66
|
+
log(`[BOT] 📌 Detected Trigger message with pushDataId: ${triggerData.pushDataId}`);
|
|
67
|
+
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
68
|
+
log(`[BOT] - Task ID: ${parsed.taskId}`);
|
|
69
|
+
try {
|
|
70
|
+
// 读取 pushData
|
|
71
|
+
const pushDataItem = await getPushDataById(triggerData.pushDataId);
|
|
72
|
+
if (!pushDataItem) {
|
|
73
|
+
error(`[BOT] ❌ pushData not found for ID: ${triggerData.pushDataId}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
log(`[BOT] ✅ Found pushData, sending direct response`);
|
|
77
|
+
log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
78
|
+
log(`[BOT] - time: ${pushDataItem.time}`);
|
|
79
|
+
log(`[BOT] - content length: ${pushDataItem.dataDetail.length} chars`);
|
|
80
|
+
const config = resolveXYConfig(cfg);
|
|
81
|
+
// 直接发送响应(final=true,不走 openclaw 流程)
|
|
82
|
+
await sendA2AResponse({
|
|
83
|
+
config,
|
|
84
|
+
sessionId: parsed.sessionId,
|
|
85
|
+
taskId: parsed.taskId,
|
|
86
|
+
messageId: parsed.messageId,
|
|
87
|
+
text: pushDataItem.dataDetail,
|
|
88
|
+
append: false,
|
|
89
|
+
final: true,
|
|
90
|
+
});
|
|
91
|
+
log(`[BOT] ✅ Trigger response sent successfully, exiting early`);
|
|
92
|
+
return; // 提前返回,不继续处理
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
error(`[BOT] ❌ Failed to handle Trigger message:`, err);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ========================================
|
|
60
100
|
// 🔑 检测steer模式和是否是第二条消息
|
|
61
101
|
const isSteerMode = cfg.messages?.queue?.mode === "steer";
|
|
62
102
|
const isSecondMessage = isSteerMode && hasActiveTask(parsed.sessionId);
|
|
@@ -81,6 +121,10 @@ export async function handleXYMessage(params) {
|
|
|
81
121
|
log(`[BOT] - Push ID preview: ${pushId.substring(0, 20)}...`);
|
|
82
122
|
log(`[BOT] - Full push_id: ${pushId}`);
|
|
83
123
|
configManager.updatePushId(parsed.sessionId, pushId);
|
|
124
|
+
// 持久化 pushId 到本地文件(异步,不阻塞主流程)
|
|
125
|
+
addPushId(pushId).catch((err) => {
|
|
126
|
+
error(`[BOT] Failed to persist pushId:`, err);
|
|
127
|
+
});
|
|
84
128
|
}
|
|
85
129
|
else {
|
|
86
130
|
log(`[BOT] ℹ️ No push_id found in message, will use config default`);
|
|
@@ -129,10 +173,9 @@ export async function handleXYMessage(params) {
|
|
|
129
173
|
// Extract text and files from parts
|
|
130
174
|
const text = extractTextFromParts(parsed.parts);
|
|
131
175
|
const fileParts = extractFileParts(parsed.parts);
|
|
132
|
-
// Download files
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const mediaPayload = buildXYMediaPayload(mediaList);
|
|
176
|
+
// Download files to local disk
|
|
177
|
+
const downloadedFiles = await downloadFilesFromParts(fileParts);
|
|
178
|
+
const mediaPayload = buildXYMediaPayload(downloadedFiles);
|
|
136
179
|
// Resolve envelope format options (following feishu pattern)
|
|
137
180
|
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
138
181
|
// Build message body with speaker prefix (following feishu pattern)
|
|
@@ -276,6 +319,8 @@ export async function handleXYMessage(params) {
|
|
|
276
319
|
/**
|
|
277
320
|
* Build media payload for inbound context.
|
|
278
321
|
* Following feishu pattern: buildFeishuMediaPayload().
|
|
322
|
+
*
|
|
323
|
+
* @param mediaList - Downloaded files with local paths
|
|
279
324
|
*/
|
|
280
325
|
function buildXYMediaPayload(mediaList) {
|
|
281
326
|
const first = mediaList[0];
|
|
@@ -284,9 +329,7 @@ function buildXYMediaPayload(mediaList) {
|
|
|
284
329
|
return {
|
|
285
330
|
MediaPath: first?.path,
|
|
286
331
|
MediaType: first?.mimeType,
|
|
287
|
-
MediaUrl: first?.path,
|
|
288
332
|
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
289
|
-
MediaUrls: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
290
333
|
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
291
334
|
};
|
|
292
335
|
}
|
package/dist/src/channel.js
CHANGED
|
@@ -14,14 +14,17 @@ import { uploadPhotoTool } from "./tools/upload-photo-tool.js";
|
|
|
14
14
|
import { xiaoyiGuiTool } from "./tools/xiaoyi-gui-tool.js";
|
|
15
15
|
import { callPhoneTool } from "./tools/call-phone-tool.js";
|
|
16
16
|
import { searchMessageTool } from "./tools/search-message-tool.js";
|
|
17
|
+
import { sendMessageTool } from "./tools/send-message-tool.js";
|
|
17
18
|
import { searchFileTool } from "./tools/search-file-tool.js";
|
|
18
19
|
import { uploadFileTool } from "./tools/upload-file-tool.js";
|
|
19
20
|
import { createAlarmTool } from "./tools/create-alarm-tool.js";
|
|
20
21
|
import { searchAlarmTool } from "./tools/search-alarm-tool.js";
|
|
21
22
|
import { modifyAlarmTool } from "./tools/modify-alarm-tool.js";
|
|
22
23
|
import { deleteAlarmTool } from "./tools/delete-alarm-tool.js";
|
|
23
|
-
import { sendMessageTool } from "./tools/send-message-tool.js";
|
|
24
24
|
import { sendFileToUserTool } from "./tools/send-file-to-user-tool.js";
|
|
25
|
+
// import { xiaoyiCollectionTool } from "./tools/xiaoyi-collection-tool.js"; // 暂时取消注册
|
|
26
|
+
import { viewPushResultTool } from "./tools/view-push-result-tool.js";
|
|
27
|
+
import { imageReadingTool } from "./tools/image-reading-tool.js";
|
|
25
28
|
/**
|
|
26
29
|
* Xiaoyi Channel Plugin for OpenClaw.
|
|
27
30
|
* Implements Xiaoyi A2A protocol with dual WebSocket connections.
|
|
@@ -61,7 +64,7 @@ export const xyPlugin = {
|
|
|
61
64
|
},
|
|
62
65
|
outbound: xyOutbound,
|
|
63
66
|
onboarding: xyOnboardingAdapter,
|
|
64
|
-
agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool,
|
|
67
|
+
agentTools: [locationTool, noteTool, searchNoteTool, modifyNoteTool, calendarTool, searchCalendarTool, searchContactTool, searchPhotoGalleryTool, uploadPhotoTool, xiaoyiGuiTool, callPhoneTool, searchMessageTool, sendMessageTool, searchFileTool, uploadFileTool, createAlarmTool, searchAlarmTool, modifyAlarmTool, deleteAlarmTool, sendFileToUserTool, viewPushResultTool, imageReadingTool],
|
|
65
68
|
messaging: {
|
|
66
69
|
normalizeTarget: (raw) => {
|
|
67
70
|
const trimmed = raw.trim();
|
|
@@ -88,10 +91,9 @@ export const xyPlugin = {
|
|
|
88
91
|
const account = resolveXYConfig(context.cfg);
|
|
89
92
|
context.setStatus?.({
|
|
90
93
|
accountId: context.accountId,
|
|
91
|
-
|
|
92
|
-
wsUrl2: account.wsUrl2,
|
|
94
|
+
wsUrl: account.wsUrl,
|
|
93
95
|
});
|
|
94
|
-
context.log?.info(`[${context.accountId}] starting xiaoyi channel (
|
|
96
|
+
context.log?.info(`[${context.accountId}] starting xiaoyi channel (wsUrl: ${account.wsUrl})`);
|
|
95
97
|
return monitorXYProvider({
|
|
96
98
|
config: context.cfg,
|
|
97
99
|
runtime: context.runtime,
|
package/dist/src/client.js
CHANGED
|
@@ -89,29 +89,16 @@ export function diagnoseAllManagers() {
|
|
|
89
89
|
console.log(`📌 Manager: ${key}`);
|
|
90
90
|
console.log(` Shutting down: ${diag.isShuttingDown}`);
|
|
91
91
|
console.log(` Total event listeners on manager: ${diag.totalEventListeners}`);
|
|
92
|
-
//
|
|
93
|
-
console.log(` 🔌
|
|
94
|
-
console.log(` - Exists: ${diag.
|
|
95
|
-
console.log(` - ReadyState: ${diag.
|
|
96
|
-
console.log(` - State connected/ready: ${diag.
|
|
97
|
-
console.log(` - Reconnect attempts: ${diag.
|
|
98
|
-
console.log(` - Listeners on WebSocket: ${diag.
|
|
99
|
-
console.log(` - Heartbeat active: ${diag.
|
|
100
|
-
console.log(` - Has reconnect timer: ${diag.
|
|
101
|
-
if (diag.
|
|
102
|
-
console.log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
|
|
103
|
-
orphanCount++;
|
|
104
|
-
}
|
|
105
|
-
// Server 2
|
|
106
|
-
console.log(` 🔌 Server2:`);
|
|
107
|
-
console.log(` - Exists: ${diag.server2.exists}`);
|
|
108
|
-
console.log(` - ReadyState: ${diag.server2.readyState}`);
|
|
109
|
-
console.log(` - State connected/ready: ${diag.server2.stateConnected}/${diag.server2.stateReady}`);
|
|
110
|
-
console.log(` - Reconnect attempts: ${diag.server2.reconnectAttempts}`);
|
|
111
|
-
console.log(` - Listeners on WebSocket: ${diag.server2.listenerCount}`);
|
|
112
|
-
console.log(` - Heartbeat active: ${diag.server2.heartbeatActive}`);
|
|
113
|
-
console.log(` - Has reconnect timer: ${diag.server2.hasReconnectTimer}`);
|
|
114
|
-
if (diag.server2.isOrphan) {
|
|
92
|
+
// Connection
|
|
93
|
+
console.log(` 🔌 Connection:`);
|
|
94
|
+
console.log(` - Exists: ${diag.connection.exists}`);
|
|
95
|
+
console.log(` - ReadyState: ${diag.connection.readyState}`);
|
|
96
|
+
console.log(` - State connected/ready: ${diag.connection.stateConnected}/${diag.connection.stateReady}`);
|
|
97
|
+
console.log(` - Reconnect attempts: ${diag.connection.reconnectAttempts}`);
|
|
98
|
+
console.log(` - Listeners on WebSocket: ${diag.connection.listenerCount}`);
|
|
99
|
+
console.log(` - Heartbeat active: ${diag.connection.heartbeatActive}`);
|
|
100
|
+
console.log(` - Has reconnect timer: ${diag.connection.hasReconnectTimer}`);
|
|
101
|
+
if (diag.connection.isOrphan) {
|
|
115
102
|
console.log(` ⚠️ ORPHAN CONNECTION DETECTED!`);
|
|
116
103
|
orphanCount++;
|
|
117
104
|
}
|
|
@@ -134,7 +121,7 @@ export function cleanupOrphanConnections() {
|
|
|
134
121
|
let cleanedCount = 0;
|
|
135
122
|
wsManagerCache.forEach((manager, key) => {
|
|
136
123
|
const diag = manager.getConnectionDiagnostics();
|
|
137
|
-
if (diag.
|
|
124
|
+
if (diag.connection.isOrphan) {
|
|
138
125
|
console.log(`🧹 Cleaning up orphan connections in manager: ${key}`);
|
|
139
126
|
manager.disconnect();
|
|
140
127
|
cleanedCount++;
|
package/dist/src/config.js
CHANGED
|
@@ -17,8 +17,8 @@ export function resolveXYConfig(cfg) {
|
|
|
17
17
|
// Return configuration with defaults
|
|
18
18
|
return {
|
|
19
19
|
enabled: xyConfig.enabled ?? false,
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// ✅ 兼容旧配置:优先使用 wsUrl,然后 wsUrl2(wsUrl1 被忽略)
|
|
21
|
+
wsUrl: xyConfig.wsUrl ?? xyConfig.wsUrl2 ?? "ws://localhost:8768/ws/link",
|
|
22
22
|
apiKey: xyConfig.apiKey,
|
|
23
23
|
uid: xyConfig.uid,
|
|
24
24
|
agentId: xyConfig.agentId,
|
|
@@ -12,6 +12,11 @@ export declare class XYFileUploadService {
|
|
|
12
12
|
* Returns the objectId (as fileId) for use in A2A messages.
|
|
13
13
|
*/
|
|
14
14
|
uploadFile(filePath: string, objectType?: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Upload a file and return its publicly accessible URL.
|
|
17
|
+
* Uses completeAndQuery endpoint to get the file URL directly.
|
|
18
|
+
*/
|
|
19
|
+
uploadFileAndGetUrl(filePath: string, objectType?: string): Promise<string>;
|
|
15
20
|
/**
|
|
16
21
|
* Upload multiple files and return their file IDs.
|
|
17
22
|
*/
|
package/dist/src/file-upload.js
CHANGED
|
@@ -105,6 +105,98 @@ export class XYFileUploadService {
|
|
|
105
105
|
return "";
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Upload a file and return its publicly accessible URL.
|
|
110
|
+
* Uses completeAndQuery endpoint to get the file URL directly.
|
|
111
|
+
*/
|
|
112
|
+
async uploadFileAndGetUrl(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
113
|
+
console.log(`[XY File Upload] Starting file upload with URL retrieval: ${filePath}`);
|
|
114
|
+
try {
|
|
115
|
+
// Read file
|
|
116
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
117
|
+
const fileName = path.basename(filePath);
|
|
118
|
+
const fileSha256 = calculateSHA256(fileBuffer);
|
|
119
|
+
const fileSize = fileBuffer.length;
|
|
120
|
+
// Phase 1: Prepare
|
|
121
|
+
console.log(`[XY File Upload] Phase 1: Prepare upload for ${fileName}`);
|
|
122
|
+
const prepareResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/prepare`, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
"x-uid": this.uid,
|
|
127
|
+
"x-api-key": this.apiKey,
|
|
128
|
+
"x-request-from": "openclaw",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
objectType,
|
|
132
|
+
fileName,
|
|
133
|
+
fileSha256,
|
|
134
|
+
fileSize,
|
|
135
|
+
fileOwnerInfo: {
|
|
136
|
+
uid: this.uid,
|
|
137
|
+
teamId: this.uid,
|
|
138
|
+
},
|
|
139
|
+
useEdge: false,
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
if (!prepareResp.ok) {
|
|
143
|
+
throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
|
|
144
|
+
}
|
|
145
|
+
const prepareData = await prepareResp.json();
|
|
146
|
+
console.log(`[XY File Upload] Prepare response:`, JSON.stringify(prepareData, null, 2));
|
|
147
|
+
if (prepareData.code !== "0") {
|
|
148
|
+
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
149
|
+
}
|
|
150
|
+
const { objectId, draftId, uploadInfos } = prepareData;
|
|
151
|
+
console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
|
|
152
|
+
// Phase 2: Upload
|
|
153
|
+
console.log(`[XY File Upload] Phase 2: Upload file data`);
|
|
154
|
+
const uploadInfo = uploadInfos[0]; // Single-part upload
|
|
155
|
+
const uploadResp = await fetch(uploadInfo.url, {
|
|
156
|
+
method: uploadInfo.method,
|
|
157
|
+
headers: uploadInfo.headers,
|
|
158
|
+
body: fileBuffer,
|
|
159
|
+
});
|
|
160
|
+
console.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
|
|
161
|
+
if (!uploadResp.ok) {
|
|
162
|
+
const uploadErrorText = await uploadResp.text();
|
|
163
|
+
console.log(`[XY File Upload] Upload error response:`, uploadErrorText);
|
|
164
|
+
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
165
|
+
}
|
|
166
|
+
console.log(`[XY File Upload] Upload complete`);
|
|
167
|
+
// Phase 3: CompleteAndQuery - get file URL
|
|
168
|
+
console.log(`[XY File Upload] Phase 3: CompleteAndQuery to get file URL`);
|
|
169
|
+
const completeResp = await fetch(`${this.baseUrl}/osms/v1/file/manager/completeAndQuery`, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
"x-uid": this.uid,
|
|
174
|
+
"x-api-key": this.apiKey,
|
|
175
|
+
"x-request-from": "openclaw",
|
|
176
|
+
},
|
|
177
|
+
body: JSON.stringify({
|
|
178
|
+
objectId,
|
|
179
|
+
draftId,
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
if (!completeResp.ok) {
|
|
183
|
+
throw new Error(`CompleteAndQuery failed: HTTP ${completeResp.status}`);
|
|
184
|
+
}
|
|
185
|
+
const completeData = await completeResp.json();
|
|
186
|
+
console.log(`[XY File Upload] CompleteAndQuery response:`, JSON.stringify(completeData, null, 2));
|
|
187
|
+
// Extract file URL from response
|
|
188
|
+
const fileUrl = completeData?.fileDetailInfo?.url || "";
|
|
189
|
+
if (!fileUrl) {
|
|
190
|
+
throw new Error("No file URL returned from completeAndQuery");
|
|
191
|
+
}
|
|
192
|
+
console.log(`[XY File Upload] File upload successful: ${fileName} → URL=${fileUrl}`);
|
|
193
|
+
return fileUrl;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error(`[XY File Upload] File upload with URL retrieval failed for ${filePath}:`, error);
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
108
200
|
/**
|
|
109
201
|
* Upload multiple files and return their file IDs.
|
|
110
202
|
*/
|
package/dist/src/formatter.d.ts
CHANGED
|
@@ -92,3 +92,17 @@ export interface SendTasksCancelResponseParams {
|
|
|
92
92
|
* Send a tasks/cancel response.
|
|
93
93
|
*/
|
|
94
94
|
export declare function sendTasksCancelResponse(params: SendTasksCancelResponseParams): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Parameters for sending a Trigger response.
|
|
97
|
+
*/
|
|
98
|
+
export interface SendTriggerResponseParams {
|
|
99
|
+
config: XYChannelConfig;
|
|
100
|
+
sessionId: string;
|
|
101
|
+
taskId: string;
|
|
102
|
+
messageId: string;
|
|
103
|
+
content: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Send a Trigger response with pushData content.
|
|
107
|
+
*/
|
|
108
|
+
export declare function sendTriggerResponse(params: SendTriggerResponseParams): Promise<void>;
|
package/dist/src/formatter.js
CHANGED
|
@@ -106,9 +106,7 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
106
106
|
taskId,
|
|
107
107
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
108
108
|
};
|
|
109
|
-
log(`[REASONING_TEXT] 📤 Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
|
|
110
109
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
111
|
-
log(`[REASONING_TEXT] ✅ Sent successfully`);
|
|
112
110
|
}
|
|
113
111
|
/**
|
|
114
112
|
* Send an A2A task status update.
|
|
@@ -293,3 +291,49 @@ export async function sendTasksCancelResponse(params) {
|
|
|
293
291
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
294
292
|
log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
295
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Send a Trigger response with pushData content.
|
|
296
|
+
*/
|
|
297
|
+
export async function sendTriggerResponse(params) {
|
|
298
|
+
const { config, sessionId, taskId, messageId, content } = params;
|
|
299
|
+
const runtime = getXYRuntime();
|
|
300
|
+
const log = runtime?.log ?? console.log;
|
|
301
|
+
const error = runtime?.error ?? console.error;
|
|
302
|
+
// Build JSON-RPC response for Trigger
|
|
303
|
+
const jsonRpcResponse = {
|
|
304
|
+
jsonrpc: "2.0",
|
|
305
|
+
id: messageId,
|
|
306
|
+
result: {
|
|
307
|
+
taskId: taskId,
|
|
308
|
+
kind: "artifact-update",
|
|
309
|
+
append: false,
|
|
310
|
+
lastChunk: true,
|
|
311
|
+
final: true,
|
|
312
|
+
artifact: {
|
|
313
|
+
artifactId: uuidv4(),
|
|
314
|
+
parts: [
|
|
315
|
+
{
|
|
316
|
+
kind: "text",
|
|
317
|
+
text: content,
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
error: {
|
|
323
|
+
code: 0,
|
|
324
|
+
message: "",
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
// Send via WebSocket
|
|
328
|
+
const wsManager = getXYWebSocketManager(config);
|
|
329
|
+
const outboundMessage = {
|
|
330
|
+
msgType: "agent_response",
|
|
331
|
+
agentId: config.agentId,
|
|
332
|
+
sessionId,
|
|
333
|
+
taskId,
|
|
334
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
335
|
+
};
|
|
336
|
+
log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
337
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
338
|
+
log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
339
|
+
}
|
package/dist/src/monitor.js
CHANGED
|
@@ -3,6 +3,7 @@ import { getXYWebSocketManager, diagnoseAllManagers, cleanupOrphanConnections, r
|
|
|
3
3
|
import { handleXYMessage } from "./bot.js";
|
|
4
4
|
import { parseA2AMessage } from "./parser.js";
|
|
5
5
|
import { hasActiveTask } from "./task-manager.js";
|
|
6
|
+
import { handleTriggerEvent } from "./trigger-handler.js";
|
|
6
7
|
/**
|
|
7
8
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
8
9
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -150,6 +151,15 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
150
151
|
const errorHandler = (err, serverId) => {
|
|
151
152
|
error(`XY gateway: ${serverId} error: ${String(err)}`);
|
|
152
153
|
};
|
|
154
|
+
const triggerEventHandler = (context) => {
|
|
155
|
+
log(`[MONITOR] 📌 Received trigger-event, dispatching to handler...`);
|
|
156
|
+
log(`[MONITOR] - sessionId: ${context.sessionId}`);
|
|
157
|
+
log(`[MONITOR] - taskId: ${context.taskId}`);
|
|
158
|
+
// 异步处理 Trigger 事件,不阻塞主流程
|
|
159
|
+
handleTriggerEvent(context, cfg, runtime, accountId).catch((err) => {
|
|
160
|
+
error(`[MONITOR] Failed to handle trigger-event:`, err);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
153
163
|
const cleanup = () => {
|
|
154
164
|
log("XY gateway: cleaning up...");
|
|
155
165
|
// 🔍 Diagnose before cleanup
|
|
@@ -166,6 +176,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
166
176
|
wsManager.off("connected", connectedHandler);
|
|
167
177
|
wsManager.off("disconnected", disconnectedHandler);
|
|
168
178
|
wsManager.off("error", errorHandler);
|
|
179
|
+
wsManager.off("trigger-event", triggerEventHandler);
|
|
169
180
|
// ✅ Disconnect the wsManager to prevent connection leaks
|
|
170
181
|
// This is safe because each gateway lifecycle should have clean connections
|
|
171
182
|
wsManager.disconnect();
|
|
@@ -195,6 +206,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
195
206
|
wsManager.on("connected", connectedHandler);
|
|
196
207
|
wsManager.on("disconnected", disconnectedHandler);
|
|
197
208
|
wsManager.on("error", errorHandler);
|
|
209
|
+
wsManager.on("trigger-event", triggerEventHandler);
|
|
198
210
|
// Start periodic health check (every 5 minutes)
|
|
199
211
|
console.log("🏥 Starting periodic health check (every 5 minutes)...");
|
|
200
212
|
healthCheckInterval = setInterval(() => {
|