@ynhcj/xiaoyi-channel 0.0.90-beta → 0.0.90-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 +6 -9
- package/dist/index.js +26 -21
- package/dist/src/bot.js +27 -4
- package/dist/src/channel.js +2 -17
- 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.js +17 -38
- package/dist/src/heartbeat.js +1 -1
- package/dist/src/login-token-handler.d.ts +8 -0
- package/dist/src/login-token-handler.js +60 -0
- package/dist/src/message-queue.d.ts +17 -0
- package/dist/src/message-queue.js +51 -0
- package/dist/src/monitor.js +75 -13
- package/dist/src/outbound.js +19 -18
- package/dist/src/provider.d.ts +2 -1
- package/dist/src/provider.js +443 -34
- package/dist/src/push.js +16 -15
- package/dist/src/reply-dispatcher.js +4 -3
- package/dist/src/runtime.d.ts +3 -11
- package/dist/src/runtime.js +6 -18
- package/dist/src/self-evolution-handler.d.ts +7 -0
- package/dist/src/self-evolution-handler.js +140 -0
- package/dist/src/self-evolution-keyword.d.ts +9 -0
- package/dist/src/self-evolution-keyword.js +147 -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/config.d.ts +4 -0
- package/dist/src/skill-retriever/config.js +23 -0
- package/dist/src/skill-retriever/hooks.d.ts +22 -0
- package/dist/src/skill-retriever/hooks.js +83 -0
- package/dist/src/skill-retriever/tool-search.d.ts +16 -0
- package/dist/src/skill-retriever/tool-search.js +173 -0
- package/dist/src/skill-retriever/types.d.ts +36 -0
- package/dist/src/skill-retriever/types.js +1 -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 -99
- 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 +17 -0
- package/dist/src/tools/get-email-tool-schema.js +12 -0
- 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 +2 -1
- package/dist/src/tools/image-reading-tool.js +86 -90
- 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 +6 -0
- package/dist/src/tools/login-token-tool.js +133 -0
- 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 +5 -0
- package/dist/src/tools/query-app-message-tool.js +135 -0
- package/dist/src/tools/query-memory-data-tool.d.ts +5 -0
- package/dist/src/tools/query-memory-data-tool.js +151 -0
- package/dist/src/tools/query-todo-task-tool.d.ts +5 -0
- package/dist/src/tools/query-todo-task-tool.js +130 -0
- 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 -0
- package/dist/src/tools/save-self-evolution-skill-tool.js +410 -0
- 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 +154 -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-manager.d.ts +21 -6
- package/dist/src/tools/session-manager.js +147 -18
- 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 +84 -88
- package/dist/src/utils/logger.js +20 -18
- package/dist/src/utils/runtime-manager.js +24 -2
- package/dist/src/utils/self-evolution-manager.d.ts +10 -0
- package/dist/src/utils/self-evolution-manager.js +69 -0
- package/dist/src/utils/tool-call-nudge-manager.d.ts +16 -0
- package/dist/src/utils/tool-call-nudge-manager.js +47 -0
- package/dist/src/websocket.d.ts +3 -0
- package/dist/src/websocket.js +96 -25
- package/openclaw.plugin.json +21 -0
- package/package.json +3 -3
package/dist/src/formatter.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
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
5
|
/**
|
|
6
6
|
* Send an A2A artifact update response.
|
|
7
7
|
*/
|
|
8
8
|
export async function sendA2AResponse(params) {
|
|
9
9
|
const { config, sessionId, taskId, messageId, text, append, final, files, errorCode, errorMessage } = params;
|
|
10
|
-
const runtime = getXYRuntime();
|
|
11
|
-
const log = runtime?.log ?? console.log;
|
|
12
|
-
const errorFn = runtime?.error ?? console.error;
|
|
13
10
|
// Build artifact update event
|
|
14
11
|
const artifact = {
|
|
15
12
|
taskId,
|
|
@@ -48,7 +45,7 @@ export async function sendA2AResponse(params) {
|
|
|
48
45
|
code: errorCode,
|
|
49
46
|
message: errorMessage ?? "任务执行异常,请重试",
|
|
50
47
|
};
|
|
51
|
-
log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
|
|
48
|
+
logger.log(`[A2A_RESPONSE] ⚠️ Including error code: ${errorCode}`);
|
|
52
49
|
}
|
|
53
50
|
// Send via WebSocket
|
|
54
51
|
const wsManager = getXYWebSocketManager(config);
|
|
@@ -60,13 +57,13 @@ export async function sendA2AResponse(params) {
|
|
|
60
57
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
61
58
|
};
|
|
62
59
|
// 📋 Log complete response body
|
|
63
|
-
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
|
|
64
|
-
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
65
|
-
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
66
|
-
log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
|
|
67
|
-
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
60
|
+
logger.log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
|
|
61
|
+
logger.log(`[A2A_RESPONSE] - append: ${append}`);
|
|
62
|
+
logger.log(`[A2A_RESPONSE] - final: ${final}`);
|
|
63
|
+
logger.log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
|
|
64
|
+
logger.log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
68
65
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
69
|
-
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
66
|
+
logger.log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
70
67
|
}
|
|
71
68
|
/**
|
|
72
69
|
* Send an A2A artifact-update with reasoningText part.
|
|
@@ -75,9 +72,6 @@ export async function sendA2AResponse(params) {
|
|
|
75
72
|
*/
|
|
76
73
|
export async function sendReasoningTextUpdate(params) {
|
|
77
74
|
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
75
|
const artifact = {
|
|
82
76
|
taskId,
|
|
83
77
|
kind: "artifact-update",
|
|
@@ -115,9 +109,6 @@ export async function sendReasoningTextUpdate(params) {
|
|
|
115
109
|
*/
|
|
116
110
|
export async function sendStatusUpdate(params) {
|
|
117
111
|
const { config, sessionId, taskId, messageId, text, state } = params;
|
|
118
|
-
const runtime = getXYRuntime();
|
|
119
|
-
const log = runtime?.log ?? console.log;
|
|
120
|
-
const error = runtime?.error ?? console.error;
|
|
121
112
|
// Build status update event following A2A protocol standard
|
|
122
113
|
const statusUpdate = {
|
|
123
114
|
taskId,
|
|
@@ -152,9 +143,9 @@ export async function sendStatusUpdate(params) {
|
|
|
152
143
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
153
144
|
};
|
|
154
145
|
// 📋 Log complete response body
|
|
155
|
-
log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
156
|
-
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
157
|
-
log(`[A2A_STATUS] - text: "${text}"`);
|
|
146
|
+
logger.log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
147
|
+
logger.log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
148
|
+
logger.log(`[A2A_STATUS] - text: "${text}"`);
|
|
158
149
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
159
150
|
}
|
|
160
151
|
/**
|
|
@@ -162,9 +153,6 @@ export async function sendStatusUpdate(params) {
|
|
|
162
153
|
*/
|
|
163
154
|
export async function sendCommand(params) {
|
|
164
155
|
const { config, sessionId, taskId, messageId, command } = params;
|
|
165
|
-
const runtime = getXYRuntime();
|
|
166
|
-
const log = runtime?.log ?? console.log;
|
|
167
|
-
const error = runtime?.error ?? console.error;
|
|
168
156
|
// Build artifact update with command as data
|
|
169
157
|
// Wrap command in commands array as per protocol requirement
|
|
170
158
|
const artifact = {
|
|
@@ -201,18 +189,15 @@ export async function sendCommand(params) {
|
|
|
201
189
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
202
190
|
};
|
|
203
191
|
// 📋 Log complete response body
|
|
204
|
-
log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
|
|
192
|
+
logger.log(`[A2A_COMMAND] 📤 Sending A2A command: taskId: ${taskId}`);
|
|
205
193
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
206
|
-
log(`[A2A_COMMAND] ✅ Command sent successfully`);
|
|
194
|
+
logger.log(`[A2A_COMMAND] ✅ Command sent successfully`);
|
|
207
195
|
}
|
|
208
196
|
/**
|
|
209
197
|
* Send a clearContext response.
|
|
210
198
|
*/
|
|
211
199
|
export async function sendClearContextResponse(params) {
|
|
212
200
|
const { config, sessionId, messageId } = params;
|
|
213
|
-
const runtime = getXYRuntime();
|
|
214
|
-
const log = runtime?.log ?? console.log;
|
|
215
|
-
const error = runtime?.error ?? console.error;
|
|
216
201
|
// Build JSON-RPC response for clearContext
|
|
217
202
|
const jsonRpcResponse = {
|
|
218
203
|
jsonrpc: "2.0",
|
|
@@ -238,16 +223,13 @@ export async function sendClearContextResponse(params) {
|
|
|
238
223
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
239
224
|
};
|
|
240
225
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
241
|
-
log(`Sent clearContext response: sessionId=${sessionId}`);
|
|
226
|
+
logger.log(`Sent clearContext response: sessionId=${sessionId}`);
|
|
242
227
|
}
|
|
243
228
|
/**
|
|
244
229
|
* Send a tasks/cancel response.
|
|
245
230
|
*/
|
|
246
231
|
export async function sendTasksCancelResponse(params) {
|
|
247
232
|
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
233
|
// Build JSON-RPC response for tasks/cancel
|
|
252
234
|
// Note: Using any to bypass type check as the response format differs from standard A2A types
|
|
253
235
|
const jsonRpcResponse = {
|
|
@@ -274,16 +256,13 @@ export async function sendTasksCancelResponse(params) {
|
|
|
274
256
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
275
257
|
};
|
|
276
258
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
277
|
-
log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
259
|
+
logger.log(`Sent tasks/cancel response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
278
260
|
}
|
|
279
261
|
/**
|
|
280
262
|
* Send a Trigger response with pushData content.
|
|
281
263
|
*/
|
|
282
264
|
export async function sendTriggerResponse(params) {
|
|
283
265
|
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
266
|
// Build JSON-RPC response for Trigger
|
|
288
267
|
const jsonRpcResponse = {
|
|
289
268
|
jsonrpc: "2.0",
|
|
@@ -318,7 +297,7 @@ export async function sendTriggerResponse(params) {
|
|
|
318
297
|
taskId,
|
|
319
298
|
msgDetail: JSON.stringify(jsonRpcResponse),
|
|
320
299
|
};
|
|
321
|
-
log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
300
|
+
logger.log(`[TRIGGER_RESPONSE] Sending Trigger response: sessionId=${sessionId}, taskId=${taskId}`);
|
|
322
301
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
323
|
-
log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
302
|
+
logger.log(`[TRIGGER_RESPONSE] Trigger response sent successfully`);
|
|
324
303
|
}
|
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,60 @@
|
|
|
1
|
+
// Login Token 事件处理器
|
|
2
|
+
// 监听 LoginTokenEvent.ClawAutoLogin 事件,将 clientId 写入 .xiaoyitoken.json
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
const TOKEN_FILE_PATH = "/home/sandbox/.openclaw/.xiaoyitoken.json";
|
|
6
|
+
/**
|
|
7
|
+
* 处理 LoginTokenEvent.ClawAutoLogin 事件
|
|
8
|
+
* 将 clientId 和当前时间戳写入 .xiaoyitoken.json 文件
|
|
9
|
+
*
|
|
10
|
+
* @param context - 事件上下文,包含 event 对象
|
|
11
|
+
* @param runtime - 运行时环境
|
|
12
|
+
*/
|
|
13
|
+
export function handleLoginTokenEvent(context, runtime) {
|
|
14
|
+
const log = runtime?.log ?? console.log;
|
|
15
|
+
const error = runtime?.error ?? console.error;
|
|
16
|
+
try {
|
|
17
|
+
const clientId = context.event?.payload?.clientId;
|
|
18
|
+
if (!clientId || typeof clientId !== "string") {
|
|
19
|
+
error("[LOGIN_TOKEN_HANDLER] invalid payload: missing clientId");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
log(`[LOGIN_TOKEN_HANDLER] received login token event, clientId=${clientId}`);
|
|
23
|
+
// Ensure directory exists
|
|
24
|
+
const dir = dirname(TOKEN_FILE_PATH);
|
|
25
|
+
if (!existsSync(dir)) {
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
let tokens = [];
|
|
29
|
+
if (existsSync(TOKEN_FILE_PATH)) {
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(TOKEN_FILE_PATH, "utf-8");
|
|
32
|
+
tokens = JSON.parse(content);
|
|
33
|
+
if (!Array.isArray(tokens)) {
|
|
34
|
+
tokens = [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
tokens = [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Check if clientId already exists
|
|
42
|
+
const now = String(Date.now());
|
|
43
|
+
const existing = tokens.find((t) => t.clientId === clientId);
|
|
44
|
+
if (existing) {
|
|
45
|
+
// Update timestamp
|
|
46
|
+
existing.timestamp = now;
|
|
47
|
+
log(`[LOGIN_TOKEN_HANDLER] updated timestamp for clientId=${clientId}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Insert new entry
|
|
51
|
+
tokens.push({ clientId, timestamp: now });
|
|
52
|
+
log(`[LOGIN_TOKEN_HANDLER] inserted new entry for clientId=${clientId}`);
|
|
53
|
+
}
|
|
54
|
+
writeFileSync(TOKEN_FILE_PATH, JSON.stringify(tokens, null, 2), "utf-8");
|
|
55
|
+
log(`[LOGIN_TOKEN_HANDLER] wrote token file: ${TOKEN_FILE_PATH}`);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
error("[LOGIN_TOKEN_HANDLER] failed to handle event:", err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -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,10 +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";
|
|
8
|
+
import { handleSelfEvolutionEvent, handleSelfEvolutionStateGetEvent } from "./self-evolution-handler.js";
|
|
9
|
+
import { handleLoginTokenEvent } from "./login-token-handler.js";
|
|
7
10
|
import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
|
|
11
|
+
import { cleanupStaleSessions, getActiveSessionCount, cleanupAllSessions } from "./tools/session-manager.js";
|
|
8
12
|
/**
|
|
9
13
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
10
14
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -47,8 +51,10 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
47
51
|
opts.setStatus({ lastEventAt: Date.now(), lastInboundAt: Date.now() });
|
|
48
52
|
}
|
|
49
53
|
: undefined;
|
|
54
|
+
// ✅ Set runtime for WebSocket manager logging before creating/getting manager
|
|
55
|
+
setClientRuntime(runtime);
|
|
50
56
|
// 🔍 Diagnose WebSocket managers before gateway start
|
|
51
|
-
|
|
57
|
+
log("🔍 [DIAGNOSTICS] Checking WebSocket managers before gateway start...");
|
|
52
58
|
diagnoseAllManagers();
|
|
53
59
|
// Get WebSocket manager (cached)
|
|
54
60
|
const wsManager = getXYWebSocketManager(account);
|
|
@@ -137,7 +143,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
137
143
|
opts.setStatus?.({ connected: true });
|
|
138
144
|
};
|
|
139
145
|
const disconnectedHandler = (serverId) => {
|
|
140
|
-
|
|
146
|
+
log(`XY gateway: ${serverId} disconnected`);
|
|
141
147
|
loggedServers.delete(serverId);
|
|
142
148
|
// ✅ Report disconnection status (only if all servers disconnected)
|
|
143
149
|
if (loggedServers.size === 0) {
|
|
@@ -156,16 +162,30 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
156
162
|
error(`[MONITOR] Failed to handle trigger-event:`, err);
|
|
157
163
|
});
|
|
158
164
|
};
|
|
165
|
+
const selfEvolutionHandler = (context) => {
|
|
166
|
+
log(`[MONITOR] Received self-evolution-event, dispatching to handler...`);
|
|
167
|
+
handleSelfEvolutionEvent(context, runtime);
|
|
168
|
+
};
|
|
169
|
+
const selfEvolutionStateGetHandler = (context) => {
|
|
170
|
+
log(`[MONITOR] Received self-evolution-state-get-event, dispatching to handler...`);
|
|
171
|
+
handleSelfEvolutionStateGetEvent(context, account, runtime, wsManager).catch((err) => {
|
|
172
|
+
error(`[MONITOR] Failed to handle self-evolution-state-get-event:`, err);
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
const loginTokenEventHandler = (context) => {
|
|
176
|
+
log(`[MONITOR] Received login-token-event, dispatching to handler...`);
|
|
177
|
+
handleLoginTokenEvent(context, runtime);
|
|
178
|
+
};
|
|
159
179
|
const cleanup = () => {
|
|
160
180
|
log("XY gateway: cleaning up...");
|
|
161
181
|
// 🔍 Diagnose before cleanup
|
|
162
|
-
|
|
182
|
+
log("🔍 [DIAGNOSTICS] Checking WebSocket managers before cleanup...");
|
|
163
183
|
diagnoseAllManagers();
|
|
164
184
|
// Stop health check interval
|
|
165
185
|
if (healthCheckInterval) {
|
|
166
186
|
clearInterval(healthCheckInterval);
|
|
167
187
|
healthCheckInterval = null;
|
|
168
|
-
|
|
188
|
+
log("⏸️ Stopped periodic health check");
|
|
169
189
|
}
|
|
170
190
|
// Remove event handlers to prevent duplicate calls on gateway restart
|
|
171
191
|
wsManager.off("message", messageHandler);
|
|
@@ -173,20 +193,53 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
173
193
|
wsManager.off("disconnected", disconnectedHandler);
|
|
174
194
|
wsManager.off("error", errorHandler);
|
|
175
195
|
wsManager.off("trigger-event", triggerEventHandler);
|
|
196
|
+
wsManager.off("self-evolution-event", selfEvolutionHandler);
|
|
197
|
+
wsManager.off("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
198
|
+
wsManager.off("login-token-event", loginTokenEventHandler);
|
|
176
199
|
// ✅ Disconnect the wsManager to prevent connection leaks
|
|
177
200
|
// This is safe because each gateway lifecycle should have clean connections
|
|
178
201
|
wsManager.disconnect();
|
|
179
202
|
// ✅ Remove manager from cache to prevent reusing dirty state
|
|
180
203
|
removeXYWebSocketManager(account);
|
|
204
|
+
// Clean up all active sessions
|
|
205
|
+
cleanupAllSessions();
|
|
181
206
|
loggedServers.clear();
|
|
182
207
|
activeMessages.clear();
|
|
183
|
-
log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages`);
|
|
208
|
+
log(`[MONITOR-HANDLER] 🧹 Cleanup complete, cleared active messages and sessions`);
|
|
184
209
|
// 🔍 Diagnose after cleanup
|
|
185
|
-
|
|
210
|
+
log("🔍 [DIAGNOSTICS] Checking WebSocket managers after cleanup...");
|
|
186
211
|
diagnoseAllManagers();
|
|
187
212
|
};
|
|
188
|
-
const handleAbort = () => {
|
|
189
|
-
log("XY gateway: abort signal received, stopping");
|
|
213
|
+
const handleAbort = async () => {
|
|
214
|
+
log("XY gateway: abort signal received, sending notifications before stopping");
|
|
215
|
+
// 📤 Send restart notification to all active sessions before disconnecting
|
|
216
|
+
try {
|
|
217
|
+
const activeBindings = getAllActiveTaskBindings();
|
|
218
|
+
if (activeBindings.length > 0) {
|
|
219
|
+
const config = resolveXYConfig(cfg);
|
|
220
|
+
const notificationText = "Gateway即将重启,重启期间可能短暂出现\u201c环境异常\u201d提示,请稍候并耐心重试~";
|
|
221
|
+
log(`[MONITOR] 📤 Sending restart notifications to ${activeBindings.length} active session(s)`);
|
|
222
|
+
const sendPromises = activeBindings.map(binding => sendA2AResponse({
|
|
223
|
+
config,
|
|
224
|
+
sessionId: binding.sessionId,
|
|
225
|
+
taskId: binding.currentTaskId,
|
|
226
|
+
messageId: binding.currentMessageId,
|
|
227
|
+
text: notificationText,
|
|
228
|
+
append: false,
|
|
229
|
+
final: true,
|
|
230
|
+
}).catch(err => {
|
|
231
|
+
error(`[MONITOR] Failed to send restart notification to session ${binding.sessionId}: ${String(err)}`);
|
|
232
|
+
}));
|
|
233
|
+
await Promise.all(sendPromises);
|
|
234
|
+
log(`[MONITOR] ✅ Restart notifications sent to ${activeBindings.length} session(s)`);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
log(`[MONITOR] No active sessions, skipping restart notifications`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
error(`[MONITOR] Error sending restart notifications: ${String(err)}`);
|
|
242
|
+
}
|
|
190
243
|
cleanup();
|
|
191
244
|
log("XY gateway stopped");
|
|
192
245
|
resolve();
|
|
@@ -203,15 +256,24 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
203
256
|
wsManager.on("disconnected", disconnectedHandler);
|
|
204
257
|
wsManager.on("error", errorHandler);
|
|
205
258
|
wsManager.on("trigger-event", triggerEventHandler);
|
|
259
|
+
wsManager.on("self-evolution-event", selfEvolutionHandler);
|
|
260
|
+
wsManager.on("self-evolution-state-get-event", selfEvolutionStateGetHandler);
|
|
261
|
+
wsManager.on("login-token-event", loginTokenEventHandler);
|
|
206
262
|
// Start periodic health check (every 6 hours)
|
|
207
|
-
|
|
263
|
+
log("🏥 Starting periodic health check (every 6 hours)...");
|
|
208
264
|
healthCheckInterval = setInterval(() => {
|
|
209
|
-
|
|
265
|
+
log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
210
266
|
diagnoseAllManagers();
|
|
211
267
|
// Auto-cleanup orphan connections
|
|
212
268
|
const cleaned = cleanupOrphanConnections();
|
|
213
269
|
if (cleaned > 0) {
|
|
214
|
-
|
|
270
|
+
log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
271
|
+
}
|
|
272
|
+
// Cleanup stale sessions (older than 10min TTL)
|
|
273
|
+
const cleanedSessions = cleanupStaleSessions();
|
|
274
|
+
const remainingSessions = getActiveSessionCount();
|
|
275
|
+
if (cleanedSessions > 0 || remainingSessions > 0) {
|
|
276
|
+
log(`🧹 [HEALTH CHECK] Sessions: cleaned=${cleanedSessions}, active=${remainingSessions}`);
|
|
215
277
|
}
|
|
216
278
|
// Cleanup stale temp files (older than 24 hours)
|
|
217
279
|
void cleanupStaleTempFiles();
|
package/dist/src/outbound.js
CHANGED
|
@@ -4,6 +4,7 @@ import { XYPushService } from "./push.js";
|
|
|
4
4
|
import { getCurrentSessionContext } from "./tools/session-manager.js";
|
|
5
5
|
import { savePushData } from "./utils/pushdata-manager.js";
|
|
6
6
|
import { getAllPushIds } from "./utils/pushid-manager.js";
|
|
7
|
+
import { logger } from "./utils/logger.js";
|
|
7
8
|
// Special marker for default push delivery when no target is specified
|
|
8
9
|
const DEFAULT_PUSH_MARKER = "default";
|
|
9
10
|
// File extension to MIME type mapping
|
|
@@ -57,7 +58,7 @@ export const xyOutbound = {
|
|
|
57
58
|
resolveTarget: ({ cfg, to, accountId, mode }) => {
|
|
58
59
|
// If no target provided, use default marker for push delivery
|
|
59
60
|
if (!to || to.trim() === "") {
|
|
60
|
-
|
|
61
|
+
logger.log(`[xyOutbound.resolveTarget] No target specified, using default push marker`);
|
|
61
62
|
return {
|
|
62
63
|
ok: true,
|
|
63
64
|
to: DEFAULT_PUSH_MARKER,
|
|
@@ -66,24 +67,24 @@ export const xyOutbound = {
|
|
|
66
67
|
const trimmedTo = to.trim();
|
|
67
68
|
// If the target doesn't contain "::", try to enhance it with taskId from session context
|
|
68
69
|
if (!trimmedTo.includes("::")) {
|
|
69
|
-
|
|
70
|
+
logger.log(`[xyOutbound.resolveTarget] Target "${trimmedTo}" missing taskId, looking up session context`);
|
|
70
71
|
// Try to get the current session context
|
|
71
72
|
const sessionContext = getCurrentSessionContext();
|
|
72
73
|
if (sessionContext && sessionContext.sessionId === trimmedTo) {
|
|
73
74
|
const enhancedTarget = `${trimmedTo}::${sessionContext.taskId}`;
|
|
74
|
-
|
|
75
|
+
logger.log(`[xyOutbound.resolveTarget] Enhanced target: ${enhancedTarget}`);
|
|
75
76
|
return {
|
|
76
77
|
ok: true,
|
|
77
78
|
to: enhancedTarget,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
else {
|
|
81
|
-
|
|
82
|
+
logger.log(`[xyOutbound.resolveTarget] Could not find matching session context for "${trimmedTo}"`);
|
|
82
83
|
// Still return the original target, but it may fail in sendMedia
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
// Otherwise, use the provided target (either already in correct format or for sendText)
|
|
86
|
-
|
|
87
|
+
logger.log(`[xyOutbound.resolveTarget] Using provided target:`, trimmedTo);
|
|
87
88
|
return {
|
|
88
89
|
ok: true,
|
|
89
90
|
to: trimmedTo,
|
|
@@ -95,36 +96,36 @@ export const xyOutbound = {
|
|
|
95
96
|
// Handle default push marker (for cron jobs without explicit target)
|
|
96
97
|
let actualTo = to;
|
|
97
98
|
if (to === DEFAULT_PUSH_MARKER) {
|
|
98
|
-
|
|
99
|
+
logger.log(`[xyOutbound.sendText] Using default push delivery (no specific target)`);
|
|
99
100
|
// For push notifications, we don't need a specific target
|
|
100
101
|
// The push service will handle it based on config
|
|
101
102
|
actualTo = config.defaultSessionId || "";
|
|
102
103
|
}
|
|
103
104
|
// 1. 持久化推送消息内容,获取 pushDataId
|
|
104
|
-
|
|
105
|
+
logger.log(`[xyOutbound.sendText] Saving push data to local storage...`);
|
|
105
106
|
let pushDataId;
|
|
106
107
|
try {
|
|
107
108
|
pushDataId = await savePushData(text);
|
|
108
|
-
|
|
109
|
+
logger.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId.substring(0, 20)}`);
|
|
109
110
|
}
|
|
110
111
|
catch (error) {
|
|
111
|
-
|
|
112
|
+
logger.error(`[xyOutbound.sendText] ❌ Failed to save push data:`, error);
|
|
112
113
|
// 如果持久化失败,仍然继续发送(不阻塞主流程)
|
|
113
114
|
pushDataId = "";
|
|
114
115
|
}
|
|
115
116
|
// 2. 读取所有 pushId
|
|
116
|
-
|
|
117
|
+
logger.log(`[xyOutbound.sendText] Loading all pushIds...`);
|
|
117
118
|
let pushIdList = [];
|
|
118
119
|
try {
|
|
119
120
|
pushIdList = await getAllPushIds();
|
|
120
|
-
|
|
121
|
+
logger.log(`[xyOutbound.sendText] ✅ Loaded ${pushIdList.length} pushIds`);
|
|
121
122
|
}
|
|
122
123
|
catch (error) {
|
|
123
|
-
|
|
124
|
+
logger.error(`[xyOutbound.sendText] ❌ Failed to load pushIds:`, error);
|
|
124
125
|
}
|
|
125
126
|
// 3. 如果 pushIdList 为空,回退到原有逻辑(使用 config pushId)
|
|
126
127
|
if (pushIdList.length === 0) {
|
|
127
|
-
|
|
128
|
+
logger.log(`[xyOutbound.sendText] ⚠️ No pushIds found, falling back to config pushId`);
|
|
128
129
|
pushIdList = [config.pushId];
|
|
129
130
|
}
|
|
130
131
|
// Create push service
|
|
@@ -134,7 +135,7 @@ export const xyOutbound = {
|
|
|
134
135
|
// Truncate push content to max length 1000
|
|
135
136
|
const pushText = text.length > 1000 ? text.slice(0, 1000) : text;
|
|
136
137
|
// 4. 遍历所有 pushId,依次发送推送通知
|
|
137
|
-
|
|
138
|
+
logger.log(`[xyOutbound.sendText] 📤 Broadcasting to ${pushIdList.length} pushId(s)...`);
|
|
138
139
|
let successCount = 0;
|
|
139
140
|
let failureCount = 0;
|
|
140
141
|
for (const pushId of pushIdList) {
|
|
@@ -142,11 +143,11 @@ export const xyOutbound = {
|
|
|
142
143
|
// 传入 pushId 和 pushDataId,使用 kind="data" 格式
|
|
143
144
|
await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
|
|
144
145
|
successCount++;
|
|
145
|
-
|
|
146
|
+
logger.log(`[xyOutbound.sendText] ✅ Sent successfully to pushId: ${pushId.substring(0, 20)}...`);
|
|
146
147
|
}
|
|
147
148
|
catch (error) {
|
|
148
149
|
failureCount++;
|
|
149
|
-
|
|
150
|
+
logger.error(`[xyOutbound.sendText] ❌ Failed to send to pushId: ${pushId.substring(0, 20)}...`, error);
|
|
150
151
|
// 单个 pushId 发送失败不影响其他,继续处理下一个
|
|
151
152
|
}
|
|
152
153
|
}
|
|
@@ -178,7 +179,7 @@ export const xyOutbound = {
|
|
|
178
179
|
if (!fileId) {
|
|
179
180
|
throw new Error(`File upload returned empty fileId for: ${mediaUrl}`);
|
|
180
181
|
}
|
|
181
|
-
|
|
182
|
+
logger.log(`[xyOutbound.sendMedia] File uploaded:`, {
|
|
182
183
|
fileId,
|
|
183
184
|
sessionId,
|
|
184
185
|
taskId,
|
|
@@ -222,7 +223,7 @@ export const xyOutbound = {
|
|
|
222
223
|
const { getXYWebSocketManager } = await import("./client.js");
|
|
223
224
|
const wsManager = getXYWebSocketManager(config);
|
|
224
225
|
await wsManager.sendMessage(sessionId, agentResponse);
|
|
225
|
-
|
|
226
|
+
logger.log(`[xyOutbound.sendMedia] WebSocket message sent successfully`);
|
|
226
227
|
// Return message info
|
|
227
228
|
return {
|
|
228
229
|
channel: "xiaoyi-channel",
|
package/dist/src/provider.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-
|
|
1
|
+
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
|
|
2
|
+
export declare function applySelfEvolutionPrompt(systemPrompt: string | undefined, enabled: boolean): string;
|
|
2
3
|
export declare const xiaoyiProvider: ProviderPlugin;
|