@ynhcj/xiaoyi-channel 0.0.30-beta → 0.0.30-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 +0 -2
- package/dist/index.js +42 -2
- package/dist/src/bot.js +55 -25
- package/dist/src/channel.js +24 -8
- package/dist/src/client.js +11 -27
- package/dist/src/config.js +2 -2
- package/dist/src/cspl/call-api.d.ts +3 -0
- package/dist/src/cspl/call-api.js +107 -0
- package/dist/src/cspl/config.d.ts +19 -0
- package/dist/src/cspl/config.js +50 -0
- package/dist/src/cspl/constants.d.ts +43 -0
- package/dist/src/cspl/constants.js +22 -0
- package/dist/src/cspl/utils.d.ts +10 -0
- package/dist/src/cspl/utils.js +57 -0
- 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 +49 -25
- package/dist/src/heartbeat.js +0 -4
- package/dist/src/monitor.js +12 -0
- package/dist/src/onboarding.d.ts +3 -4
- package/dist/src/onboarding.js +2 -2
- package/dist/src/outbound.d.ts +2 -1
- 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 +39 -5
- package/dist/src/steer-injector.d.ts +16 -0
- package/dist/src/steer-injector.js +74 -0
- package/dist/src/thread-bindings.d.ts +54 -0
- package/dist/src/thread-bindings.js +214 -0
- package/dist/src/tools/calendar-tool.js +5 -38
- package/dist/src/tools/call-phone-tool.js +1 -42
- package/dist/src/tools/create-alarm-tool.js +9 -104
- package/dist/src/tools/delete-alarm-tool.js +5 -69
- package/dist/src/tools/image-reading-tool.d.ts +5 -0
- package/dist/src/tools/image-reading-tool.js +328 -0
- package/dist/src/tools/location-tool.js +6 -40
- package/dist/src/tools/modify-alarm-tool.js +8 -117
- package/dist/src/tools/modify-note-tool.js +3 -41
- package/dist/src/tools/note-tool.js +31 -22
- package/dist/src/tools/search-alarm-tool.js +14 -118
- package/dist/src/tools/search-calendar-tool.js +8 -82
- package/dist/src/tools/search-contact-tool.js +2 -55
- package/dist/src/tools/search-file-tool.js +4 -61
- package/dist/src/tools/search-message-tool.js +2 -59
- package/dist/src/tools/search-note-tool.js +4 -22
- package/dist/src/tools/search-photo-gallery-tool.js +38 -59
- package/dist/src/tools/send-file-to-user-tool.js +0 -39
- package/dist/src/tools/send-message-tool.js +5 -56
- package/dist/src/tools/session-manager.js +0 -45
- package/dist/src/tools/upload-file-tool.js +0 -49
- package/dist/src/tools/upload-photo-tool.js +0 -42
- package/dist/src/tools/view-push-result-tool.d.ts +5 -0
- package/dist/src/tools/view-push-result-tool.js +107 -0
- package/dist/src/tools/xiaoyi-collection-tool.d.ts +5 -0
- package/dist/src/tools/xiaoyi-collection-tool.js +112 -0
- package/dist/src/tools/xiaoyi-gui-tool.js +0 -34
- 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 +233 -271
- package/package.json +2 -2
- package/dist/src/tools/search-photo-tool.d.ts +0 -9
- package/dist/src/tools/search-photo-tool.js +0 -270
- package/dist/src/utils/session.d.ts +0 -34
- package/dist/src/utils/session.js +0 -50
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// CSPL Hook 工具函数
|
|
2
|
+
import { MAX_TEXT_LENGTH, regex } from "./constants.js";
|
|
3
|
+
export function filterText(text) {
|
|
4
|
+
if (!text)
|
|
5
|
+
return "";
|
|
6
|
+
return text.replace(new RegExp(regex.source, "g"), "");
|
|
7
|
+
}
|
|
8
|
+
export function validateAndTruncateText(text, maxLength) {
|
|
9
|
+
if (text.length > maxLength) {
|
|
10
|
+
const halfMaxLength = Math.floor(maxLength / 2);
|
|
11
|
+
const startText = text.substring(0, halfMaxLength);
|
|
12
|
+
const endText = text.substring(text.length - halfMaxLength);
|
|
13
|
+
return { text: startText + endText, truncated: true };
|
|
14
|
+
}
|
|
15
|
+
return { text, truncated: false };
|
|
16
|
+
}
|
|
17
|
+
export function extractResultText(event, toolName) {
|
|
18
|
+
const resultTexts = [];
|
|
19
|
+
if (toolName === "web_fetch") {
|
|
20
|
+
if (event.result?.details?.text) {
|
|
21
|
+
resultTexts.push(event.result.details.text);
|
|
22
|
+
}
|
|
23
|
+
return resultTexts.length > 0 ? resultTexts.join("; ") : "";
|
|
24
|
+
}
|
|
25
|
+
if (event.result?.content && Array.isArray(event.result.content)) {
|
|
26
|
+
for (const item of event.result.content) {
|
|
27
|
+
if (item?.text) {
|
|
28
|
+
resultTexts.push(item.text);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return resultTexts.length > 0 ? resultTexts.join("; ") : "";
|
|
33
|
+
}
|
|
34
|
+
export function processText(resultText) {
|
|
35
|
+
const questionText = filterText(resultText);
|
|
36
|
+
const { text: finalText } = validateAndTruncateText(questionText, MAX_TEXT_LENGTH);
|
|
37
|
+
return finalText;
|
|
38
|
+
}
|
|
39
|
+
export function parseSecurityResult(response) {
|
|
40
|
+
if (response === null || response === undefined) {
|
|
41
|
+
throw new Error("Response is null or undefined");
|
|
42
|
+
}
|
|
43
|
+
if (!response.data || typeof response.data !== "object") {
|
|
44
|
+
throw new Error("Response.data is missing or not an object");
|
|
45
|
+
}
|
|
46
|
+
const securityResult = response.data.securityResult;
|
|
47
|
+
if (typeof securityResult !== "string") {
|
|
48
|
+
throw new Error("Response.data.securityResult is missing or not a string");
|
|
49
|
+
}
|
|
50
|
+
if (securityResult !== securityResult.trim()) {
|
|
51
|
+
throw new Error("Response.data.securityResult contains leading or trailing spaces");
|
|
52
|
+
}
|
|
53
|
+
if (securityResult !== "ACCEPT" && securityResult !== "REJECT") {
|
|
54
|
+
throw new Error(`Response.data.securityResult must be "ACCEPT" or "REJECT". Actual: "${securityResult}"`);
|
|
55
|
+
}
|
|
56
|
+
return { status: securityResult };
|
|
57
|
+
}
|
|
@@ -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
|
@@ -52,18 +52,11 @@ export async function sendA2AResponse(params) {
|
|
|
52
52
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
53
53
|
};
|
|
54
54
|
// 📋 Log complete response body
|
|
55
|
-
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response
|
|
56
|
-
log(`[A2A_RESPONSE] - sessionId: ${sessionId}`);
|
|
57
|
-
log(`[A2A_RESPONSE] - taskId: ${taskId}`);
|
|
58
|
-
log(`[A2A_RESPONSE] - messageId: ${messageId}`);
|
|
55
|
+
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
|
|
59
56
|
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
60
57
|
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
61
|
-
log(`[A2A_RESPONSE] - text
|
|
58
|
+
log(`[A2A_RESPONSE] - text: ${text}`);
|
|
62
59
|
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
63
|
-
log(`[A2A_RESPONSE] 📦 Complete outbound message:`);
|
|
64
|
-
log(JSON.stringify(outboundMessage, null, 2));
|
|
65
|
-
log(`[A2A_RESPONSE] 📦 JSON-RPC response body:`);
|
|
66
|
-
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
67
60
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
68
61
|
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
69
62
|
}
|
|
@@ -106,9 +99,7 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
106
99
|
taskId,
|
|
107
100
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
108
101
|
};
|
|
109
|
-
log(`[REASONING_TEXT] 📤 Sending reasoningText update: sessionId=${sessionId}, taskId=${taskId}, text.length=${text.length}`);
|
|
110
102
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
111
|
-
log(`[REASONING_TEXT] ✅ Sent successfully`);
|
|
112
103
|
}
|
|
113
104
|
/**
|
|
114
105
|
* Send an A2A task status update.
|
|
@@ -154,15 +145,10 @@ export async function sendStatusUpdate(params) {
|
|
|
154
145
|
};
|
|
155
146
|
// 📋 Log complete response body
|
|
156
147
|
log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
157
|
-
log(`[A2A_STATUS] - sessionId: ${sessionId}`);
|
|
158
148
|
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
159
149
|
log(`[A2A_STATUS] - messageId: ${messageId}`);
|
|
160
150
|
log(`[A2A_STATUS] - state: ${state}`);
|
|
161
151
|
log(`[A2A_STATUS] - text: "${text}"`);
|
|
162
|
-
log(`[A2A_STATUS] 📦 Complete outbound message:`);
|
|
163
|
-
log(JSON.stringify(outboundMessage, null, 2));
|
|
164
|
-
log(`[A2A_STATUS] 📦 JSON-RPC response body:`);
|
|
165
|
-
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
166
152
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
167
153
|
log(`[A2A_STATUS] ✅ Status update sent successfully`);
|
|
168
154
|
}
|
|
@@ -210,15 +196,7 @@ export async function sendCommand(params) {
|
|
|
210
196
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
211
197
|
};
|
|
212
198
|
// 📋 Log complete response body
|
|
213
|
-
log(`[A2A_COMMAND] 📤 Sending A2A command
|
|
214
|
-
log(`[A2A_COMMAND] - sessionId: ${sessionId}`);
|
|
215
|
-
log(`[A2A_COMMAND] - taskId: ${taskId}`);
|
|
216
|
-
log(`[A2A_COMMAND] - messageId: ${messageId}`);
|
|
217
|
-
log(`[A2A_COMMAND] - command: ${command.header.namespace}::${command.header.name}`);
|
|
218
|
-
log(`[A2A_COMMAND] 📦 Complete outbound message:`);
|
|
219
|
-
log(JSON.stringify(outboundMessage, null, 2));
|
|
220
|
-
log(`[A2A_COMMAND] 📦 JSON-RPC response body:`);
|
|
221
|
-
log(JSON.stringify(jsonRpcResponse, null, 2));
|
|
199
|
+
log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
|
|
222
200
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
223
201
|
log(`[A2A_COMMAND] ✅ Command sent successfully`);
|
|
224
202
|
}
|
|
@@ -293,3 +271,49 @@ export async function sendTasksCancelResponse(params) {
|
|
|
293
271
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
294
272
|
log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
295
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Send a Trigger response with pushData content.
|
|
276
|
+
*/
|
|
277
|
+
export async function sendTriggerResponse(params) {
|
|
278
|
+
const { config, sessionId, taskId, messageId, content } = params;
|
|
279
|
+
const runtime = getXYRuntime();
|
|
280
|
+
const log = runtime?.log ?? console.log;
|
|
281
|
+
const error = runtime?.error ?? console.error;
|
|
282
|
+
// Build JSON-RPC response for Trigger
|
|
283
|
+
const jsonRpcResponse = {
|
|
284
|
+
jsonrpc: "2.0",
|
|
285
|
+
id: messageId,
|
|
286
|
+
result: {
|
|
287
|
+
taskId: taskId,
|
|
288
|
+
kind: "artifact-update",
|
|
289
|
+
append: false,
|
|
290
|
+
lastChunk: true,
|
|
291
|
+
final: true,
|
|
292
|
+
artifact: {
|
|
293
|
+
artifactId: uuidv4(),
|
|
294
|
+
parts: [
|
|
295
|
+
{
|
|
296
|
+
kind: "text",
|
|
297
|
+
text: content,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
error: {
|
|
303
|
+
code: 0,
|
|
304
|
+
message: "",
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
// Send via WebSocket
|
|
308
|
+
const wsManager = getXYWebSocketManager(config);
|
|
309
|
+
const outboundMessage = {
|
|
310
|
+
msgType: "agent_response",
|
|
311
|
+
agentId: config.agentId,
|
|
312
|
+
sessionId,
|
|
313
|
+
taskId,
|
|
314
|
+
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
315
|
+
};
|
|
316
|
+
log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
317
|
+
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
318
|
+
log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
319
|
+
}
|
package/dist/src/heartbeat.js
CHANGED
|
@@ -72,18 +72,14 @@ export class HeartbeatManager {
|
|
|
72
72
|
}
|
|
73
73
|
try {
|
|
74
74
|
// Send application-level heartbeat message
|
|
75
|
-
console.log(`[WS-${this.serverName}-SEND] Sending heartbeat frame:`, this.config.message);
|
|
76
75
|
this.ws.send(this.config.message);
|
|
77
|
-
console.log(`[WS-${this.serverName}-SEND] Heartbeat message sent, size: ${this.config.message.length} bytes`);
|
|
78
76
|
// Send protocol-level ping
|
|
79
77
|
this.ws.ping();
|
|
80
|
-
console.log(`[WS-${this.serverName}-SEND] Protocol-level ping sent`);
|
|
81
78
|
// Setup timeout timer
|
|
82
79
|
this.timeoutTimer = setTimeout(() => {
|
|
83
80
|
this.error(`Heartbeat timeout for ${this.serverName}`);
|
|
84
81
|
this.onTimeout();
|
|
85
82
|
}, this.config.timeout);
|
|
86
|
-
this.log(`[DEBUG] Heartbeat sent for ${this.serverName}`);
|
|
87
83
|
}
|
|
88
84
|
catch (error) {
|
|
89
85
|
this.error(`Failed to send heartbeat for ${this.serverName}:`, error);
|
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(() => {
|
package/dist/src/onboarding.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
|
|
2
1
|
/**
|
|
3
|
-
* XY Channel Onboarding Adapter
|
|
4
|
-
*
|
|
2
|
+
* XY Channel Onboarding Adapter (DISABLED - not currently used)
|
|
3
|
+
* NOTE: This is kept for future reference. Xiaoyi uses simple single-account config.
|
|
5
4
|
*/
|
|
6
|
-
export declare const xyOnboardingAdapter:
|
|
5
|
+
export declare const xyOnboardingAdapter: any;
|
package/dist/src/onboarding.js
CHANGED
|
@@ -152,8 +152,8 @@ async function configure({ cfg, prompter, }) {
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
|
-
* XY Channel Onboarding Adapter
|
|
156
|
-
*
|
|
155
|
+
* XY Channel Onboarding Adapter (DISABLED - not currently used)
|
|
156
|
+
* NOTE: This is kept for future reference. Xiaoyi uses simple single-account config.
|
|
157
157
|
*/
|
|
158
158
|
export const xyOnboardingAdapter = {
|
|
159
159
|
channel,
|
package/dist/src/outbound.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type ChannelOutboundAdapter = any;
|
|
2
2
|
/**
|
|
3
3
|
* Outbound adapter for sending messages from OpenClaw to XY.
|
|
4
4
|
* Uses Push service for direct message delivery.
|
|
5
5
|
*/
|
|
6
6
|
export declare const xyOutbound: ChannelOutboundAdapter;
|
|
7
|
+
export {};
|
package/dist/src/outbound.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { resolveXYConfig } from "./config.js";
|
|
2
|
+
import { XYFileUploadService } from "./file-upload.js";
|
|
2
3
|
import { XYPushService } from "./push.js";
|
|
3
4
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
5
|
+
import { savePushData } from "./utils/pushdata-manager.js";
|
|
6
|
+
import { getAllPushIds } from "./utils/pushid-manager.js";
|
|
4
7
|
// Special marker for default push delivery when no target is specified
|
|
5
8
|
const DEFAULT_PUSH_MARKER = "default";
|
|
6
9
|
// File extension to MIME type mapping
|
|
@@ -104,19 +107,63 @@ export const xyOutbound = {
|
|
|
104
107
|
// The push service will handle it based on config
|
|
105
108
|
actualTo = config.defaultSessionId || "";
|
|
106
109
|
}
|
|
110
|
+
// 1. 持久化推送消息内容,获取 pushDataId
|
|
111
|
+
console.log(`[xyOutbound.sendText] Saving push data to local storage...`);
|
|
112
|
+
let pushDataId;
|
|
113
|
+
try {
|
|
114
|
+
pushDataId = await savePushData(text);
|
|
115
|
+
console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId}`);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to save push data:`, error);
|
|
119
|
+
// 如果持久化失败,仍然继续发送(不阻塞主流程)
|
|
120
|
+
pushDataId = "";
|
|
121
|
+
}
|
|
122
|
+
// 2. 读取所有 pushId
|
|
123
|
+
console.log(`[xyOutbound.sendText] Loading all pushIds...`);
|
|
124
|
+
let pushIdList = [];
|
|
125
|
+
try {
|
|
126
|
+
pushIdList = await getAllPushIds();
|
|
127
|
+
console.log(`[xyOutbound.sendText] ✅ Loaded ${pushIdList.length} pushIds`);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to load pushIds:`, error);
|
|
131
|
+
}
|
|
132
|
+
// 3. 如果 pushIdList 为空,回退到原有逻辑(使用 config pushId)
|
|
133
|
+
if (pushIdList.length === 0) {
|
|
134
|
+
console.log(`[xyOutbound.sendText] ⚠️ No pushIds found, falling back to config pushId`);
|
|
135
|
+
pushIdList = [config.pushId];
|
|
136
|
+
}
|
|
107
137
|
// Create push service
|
|
108
138
|
const pushService = new XYPushService(config);
|
|
109
139
|
// Extract title (first 57 chars or first line)
|
|
110
140
|
const title = text.split("\n")[0].slice(0, 57);
|
|
111
141
|
// Truncate push content to max length 1000
|
|
112
142
|
const pushText = text.length > 1000 ? text.slice(0, 1000) : text;
|
|
113
|
-
//
|
|
114
|
-
|
|
143
|
+
// 4. 遍历所有 pushId,依次发送推送通知
|
|
144
|
+
console.log(`[xyOutbound.sendText] 📤 Broadcasting to ${pushIdList.length} pushId(s)...`);
|
|
145
|
+
let successCount = 0;
|
|
146
|
+
let failureCount = 0;
|
|
147
|
+
for (const pushId of pushIdList) {
|
|
148
|
+
try {
|
|
149
|
+
console.log(`[xyOutbound.sendText] Sending to pushId: ${pushId.substring(0, 20)}...`);
|
|
150
|
+
// 传入 pushId 和 pushDataId,使用 kind="data" 格式
|
|
151
|
+
await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
|
|
152
|
+
successCount++;
|
|
153
|
+
console.log(`[xyOutbound.sendText] ✅ Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
failureCount++;
|
|
157
|
+
console.error(`[xyOutbound.sendText] ❌ Failed to send to pushId: ${pushId.substring(0, 20)}...`, error);
|
|
158
|
+
// 单个 pushId 发送失败不影响其他,继续处理下一个
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
console.log(`[xyOutbound.sendText] 📊 Broadcast summary: ${successCount} success, ${failureCount} failures`);
|
|
115
162
|
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
116
163
|
// Return message info
|
|
117
164
|
return {
|
|
118
165
|
channel: "xiaoyi-channel",
|
|
119
|
-
messageId: Date.now().toString(),
|
|
166
|
+
messageId: pushDataId || Date.now().toString(),
|
|
120
167
|
chatId: actualTo,
|
|
121
168
|
};
|
|
122
169
|
},
|
|
@@ -129,94 +176,81 @@ export const xyOutbound = {
|
|
|
129
176
|
mediaUrl,
|
|
130
177
|
mediaLocalRoots,
|
|
131
178
|
});
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
179
|
+
// Parse to: "sessionId::taskId"
|
|
180
|
+
const parts = to.split("::");
|
|
181
|
+
if (parts.length !== 2) {
|
|
182
|
+
throw new Error(`Invalid to format: "${to}". Expected "sessionId::taskId"`);
|
|
183
|
+
}
|
|
184
|
+
const [sessionId, taskId] = parts;
|
|
185
|
+
// Resolve configuration
|
|
186
|
+
const config = resolveXYConfig(cfg);
|
|
187
|
+
// Create upload service
|
|
188
|
+
const uploadService = new XYFileUploadService(config.fileUploadUrl, config.apiKey, config.uid);
|
|
189
|
+
// Validate mediaUrl
|
|
190
|
+
if (!mediaUrl) {
|
|
191
|
+
throw new Error("mediaUrl is required for sendMedia");
|
|
192
|
+
}
|
|
193
|
+
// Upload file
|
|
194
|
+
const fileId = await uploadService.uploadFile(mediaUrl);
|
|
195
|
+
// Check if fileId is empty
|
|
196
|
+
if (!fileId) {
|
|
197
|
+
console.log(`[xyOutbound.sendMedia] ⚠️ File upload failed: fileId is empty, aborting sendMedia`);
|
|
198
|
+
return {
|
|
199
|
+
channel: "xiaoyi-channel",
|
|
200
|
+
messageId: "",
|
|
201
|
+
chatId: to,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
console.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
205
|
+
fileId,
|
|
206
|
+
sessionId,
|
|
207
|
+
taskId,
|
|
208
|
+
});
|
|
209
|
+
// Get filename and mime type from mediaUrl
|
|
210
|
+
// mediaUrl may be a local file path or URL
|
|
211
|
+
const fileName = mediaUrl.split("/").pop() || "unknown";
|
|
212
|
+
const mimeType = getMimeTypeFromFilename(fileName);
|
|
213
|
+
// Build agent_response message
|
|
214
|
+
const agentResponse = {
|
|
215
|
+
msgType: "agent_response",
|
|
216
|
+
agentId: config.agentId,
|
|
217
|
+
sessionId: sessionId,
|
|
218
|
+
taskId: taskId,
|
|
219
|
+
msgDetail: JSON.stringify({
|
|
220
|
+
jsonrpc: "2.0",
|
|
221
|
+
id: taskId,
|
|
222
|
+
result: {
|
|
223
|
+
kind: "artifact-update",
|
|
224
|
+
append: true,
|
|
225
|
+
lastChunk: false,
|
|
226
|
+
final: false,
|
|
227
|
+
artifact: {
|
|
228
|
+
artifactId: taskId,
|
|
229
|
+
parts: [
|
|
230
|
+
{
|
|
231
|
+
kind: "file",
|
|
232
|
+
file: {
|
|
233
|
+
name: fileName,
|
|
234
|
+
mimeType: mimeType,
|
|
235
|
+
fileId: fileId,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
error: { code: 0 },
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
// Get WebSocket manager and send message
|
|
245
|
+
const { getXYWebSocketManager } = await import("./client.js");
|
|
246
|
+
const wsManager = getXYWebSocketManager(config);
|
|
247
|
+
await wsManager.sendMessage(sessionId, agentResponse);
|
|
248
|
+
console.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
249
|
+
// Return message info
|
|
136
250
|
return {
|
|
137
251
|
channel: "xiaoyi-channel",
|
|
138
|
-
messageId:
|
|
252
|
+
messageId: fileId,
|
|
139
253
|
chatId: to,
|
|
140
254
|
};
|
|
141
|
-
// // Parse to: "sessionId::taskId"
|
|
142
|
-
// const parts = to.split("::");
|
|
143
|
-
// if (parts.length !== 2) {
|
|
144
|
-
// throw new Error(`Invalid to format: "${to}". Expected "sessionId::taskId"`);
|
|
145
|
-
// }
|
|
146
|
-
// const [sessionId, taskId] = parts;
|
|
147
|
-
// // Resolve configuration
|
|
148
|
-
// const config = resolveXYConfig(cfg);
|
|
149
|
-
// // Create upload service
|
|
150
|
-
// const uploadService = new XYFileUploadService(
|
|
151
|
-
// config.fileUploadUrl,
|
|
152
|
-
// config.apiKey,
|
|
153
|
-
// config.uid
|
|
154
|
-
// );
|
|
155
|
-
// // Validate mediaUrl
|
|
156
|
-
// if (!mediaUrl) {
|
|
157
|
-
// throw new Error("mediaUrl is required for sendMedia");
|
|
158
|
-
// }
|
|
159
|
-
// // Upload file
|
|
160
|
-
// const fileId = await uploadService.uploadFile(mediaUrl);
|
|
161
|
-
// // Check if fileId is empty
|
|
162
|
-
// if (!fileId) {
|
|
163
|
-
// console.log(`[xyOutbound.sendMedia] ⚠️ File upload failed: fileId is empty, aborting sendMedia`);
|
|
164
|
-
// return {
|
|
165
|
-
// channel: "xiaoyi-channel",
|
|
166
|
-
// messageId: "",
|
|
167
|
-
// chatId: to,
|
|
168
|
-
// };
|
|
169
|
-
// }
|
|
170
|
-
// console.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
171
|
-
// fileId,
|
|
172
|
-
// sessionId,
|
|
173
|
-
// taskId,
|
|
174
|
-
// });
|
|
175
|
-
// // Get filename and mime type from mediaUrl
|
|
176
|
-
// // mediaUrl may be a local file path or URL
|
|
177
|
-
// const fileName = mediaUrl.split("/").pop() || "unknown";
|
|
178
|
-
// const mimeType = getMimeTypeFromFilename(fileName);
|
|
179
|
-
// // Build agent_response message
|
|
180
|
-
// const agentResponse: OutboundWebSocketMessage = {
|
|
181
|
-
// msgType: "agent_response",
|
|
182
|
-
// agentId: config.agentId,
|
|
183
|
-
// sessionId: sessionId,
|
|
184
|
-
// taskId: taskId,
|
|
185
|
-
// msgDetail: JSON.stringify({
|
|
186
|
-
// jsonrpc: "2.0",
|
|
187
|
-
// id: taskId,
|
|
188
|
-
// result: {
|
|
189
|
-
// kind: "artifact-update",
|
|
190
|
-
// append: true,
|
|
191
|
-
// lastChunk: false,
|
|
192
|
-
// final: false,
|
|
193
|
-
// artifact: {
|
|
194
|
-
// artifactId: taskId,
|
|
195
|
-
// parts: [
|
|
196
|
-
// {
|
|
197
|
-
// kind: "file",
|
|
198
|
-
// file: {
|
|
199
|
-
// name: fileName,
|
|
200
|
-
// mimeType: mimeType,
|
|
201
|
-
// fileId: fileId,
|
|
202
|
-
// },
|
|
203
|
-
// },
|
|
204
|
-
// ],
|
|
205
|
-
// },
|
|
206
|
-
// },
|
|
207
|
-
// error: { code: 0 },
|
|
208
|
-
// }),
|
|
209
|
-
// };
|
|
210
|
-
// // Get WebSocket manager and send message
|
|
211
|
-
// const { getXYWebSocketManager } = await import("./client.js");
|
|
212
|
-
// const wsManager = getXYWebSocketManager(config);
|
|
213
|
-
// await wsManager.sendMessage(sessionId, agentResponse);
|
|
214
|
-
// console.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
215
|
-
// // Return message info
|
|
216
|
-
// return {
|
|
217
|
-
// channel: "xiaoyi-channel",
|
|
218
|
-
// messageId: fileId,
|
|
219
|
-
// chatId: to,
|
|
220
|
-
// };
|
|
221
255
|
},
|
|
222
256
|
};
|
package/dist/src/parser.d.ts
CHANGED
|
@@ -43,6 +43,13 @@ export declare function isTasksCancelMessage(method: string): boolean;
|
|
|
43
43
|
* Looks for push_id in data parts under variables.systemVariables.push_id
|
|
44
44
|
*/
|
|
45
45
|
export declare function extractPushId(parts: A2AMessagePart[]): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Extract Trigger event data from message parts.
|
|
48
|
+
* Looks for Trigger events with pushDataId in data parts.
|
|
49
|
+
*/
|
|
50
|
+
export declare function extractTriggerData(parts: A2AMessagePart[]): {
|
|
51
|
+
pushDataId: string;
|
|
52
|
+
} | null;
|
|
46
53
|
/**
|
|
47
54
|
* Validate A2A request structure.
|
|
48
55
|
*/
|