@ynhcj/xiaoyi-channel 0.0.53-beta → 0.0.55-beta
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 +0 -24
- package/dist/src/client.js +0 -6
- package/dist/src/cspl/call-api.js +0 -21
- package/dist/src/file-upload.js +1 -11
- package/dist/src/formatter.js +1 -4
- package/dist/src/monitor.js +7 -10
- package/dist/src/outbound.js +1 -19
- package/dist/src/push.js +0 -21
- package/dist/src/reply-dispatcher.d.ts +4 -0
- package/dist/src/reply-dispatcher.js +12 -10
- package/dist/src/tools/send-file-to-user-tool.js +1 -0
- package/dist/src/websocket.js +15 -9
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -26,8 +26,6 @@ export async function handleXYMessage(params) {
|
|
|
26
26
|
try {
|
|
27
27
|
// Check for special messages BEFORE parsing (these have different param structures)
|
|
28
28
|
const messageMethod = message.method;
|
|
29
|
-
log(`[BOT-ENTRY] <<<<<<< Received message with method: ${messageMethod}, id: ${message.id} >>>>>>>`);
|
|
30
|
-
log(`[BOT-ENTRY] Stack trace for debugging:`, new Error().stack?.split('\n').slice(1, 4).join('\n'));
|
|
31
29
|
// Handle clearContext messages (params only has sessionId)
|
|
32
30
|
if (messageMethod === "clearContext" || messageMethod === "clear_context") {
|
|
33
31
|
const sessionId = message.params?.sessionId;
|
|
@@ -78,8 +76,6 @@ export async function handleXYMessage(params) {
|
|
|
78
76
|
}
|
|
79
77
|
log(`[BOT] ✅ Found pushData, sending direct response`);
|
|
80
78
|
log(`[BOT] - pushDataId: ${pushDataItem.pushDataId}`);
|
|
81
|
-
log(`[BOT] - time: ${pushDataItem.time}`);
|
|
82
|
-
log(`[BOT] - content length: ${pushDataItem.dataDetail.length} chars`);
|
|
83
79
|
const config = resolveXYConfig(cfg);
|
|
84
80
|
// 直接发送响应(final=true,不走 openclaw 流程)
|
|
85
81
|
await sendA2AResponse({
|
|
@@ -120,9 +116,6 @@ export async function handleXYMessage(params) {
|
|
|
120
116
|
const pushId = extractPushId(parsed.parts);
|
|
121
117
|
if (pushId) {
|
|
122
118
|
log(`[BOT] 📌 Extracted push_id from user message`);
|
|
123
|
-
log(`[BOT] - Session ID: ${parsed.sessionId}`);
|
|
124
|
-
log(`[BOT] - Push ID preview: ${pushId.substring(0, 20)}...`);
|
|
125
|
-
log(`[BOT] - Full push_id: ${pushId}`);
|
|
126
119
|
configManager.updatePushId(parsed.sessionId, pushId);
|
|
127
120
|
// 持久化 pushId 到本地文件(异步,不阻塞主流程)
|
|
128
121
|
addPushId(pushId).catch((err) => {
|
|
@@ -147,12 +140,6 @@ export async function handleXYMessage(params) {
|
|
|
147
140
|
},
|
|
148
141
|
});
|
|
149
142
|
log(`xy: resolved route accountId=${route.accountId}, sessionKey=${route.sessionKey}`);
|
|
150
|
-
// 🔑 注册session(带引用计数)
|
|
151
|
-
log(`[BOT] 📝 About to register session for tools...`);
|
|
152
|
-
log(`[BOT] - sessionKey: ${route.sessionKey}`);
|
|
153
|
-
log(`[BOT] - sessionId: ${parsed.sessionId}`);
|
|
154
|
-
log(`[BOT] - taskId: ${parsed.taskId}`);
|
|
155
|
-
log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
|
|
156
143
|
registerSession(route.sessionKey, {
|
|
157
144
|
config,
|
|
158
145
|
sessionId: parsed.sessionId,
|
|
@@ -160,7 +147,6 @@ export async function handleXYMessage(params) {
|
|
|
160
147
|
messageId: parsed.messageId,
|
|
161
148
|
agentId: route.accountId,
|
|
162
149
|
});
|
|
163
|
-
log(`[BOT] ✅ Session registered for tools`);
|
|
164
150
|
// 🔑 发送初始状态更新(第二条消息也要发,用新taskId)
|
|
165
151
|
log(`[STATUS] Sending initial status update for session ${parsed.sessionId}`);
|
|
166
152
|
void sendStatusUpdate({
|
|
@@ -221,9 +207,7 @@ export async function handleXYMessage(params) {
|
|
|
221
207
|
});
|
|
222
208
|
// 🔑 创建dispatcher(dispatcher会自动使用动态taskId)
|
|
223
209
|
log(`[BOT-DISPATCHER] 🎯 Creating reply dispatcher`);
|
|
224
|
-
log(`[BOT-DISPATCHER] - session: ${parsed.sessionId}`);
|
|
225
210
|
log(`[BOT-DISPATCHER] - taskId: ${parsed.taskId}`);
|
|
226
|
-
log(`[BOT-DISPATCHER] - isSecondMessage: ${isSecondMessage}`);
|
|
227
211
|
const { dispatcher, replyOptions, markDispatchIdle, startStatusInterval } = createXYReplyDispatcher({
|
|
228
212
|
cfg,
|
|
229
213
|
runtime,
|
|
@@ -233,19 +217,12 @@ export async function handleXYMessage(params) {
|
|
|
233
217
|
accountId: route.accountId,
|
|
234
218
|
isSteerFollower: isSecondMessage, // 🔑 标记第二条消息
|
|
235
219
|
});
|
|
236
|
-
log(`[BOT-DISPATCHER] ✅ Reply dispatcher created successfully`);
|
|
237
220
|
// 🔑 只有第一条消息启动状态定时器
|
|
238
221
|
// 第二条消息会很快返回,不需要定时器
|
|
239
222
|
if (!isSecondMessage) {
|
|
240
223
|
startStatusInterval();
|
|
241
224
|
log(`[BOT-DISPATCHER] ✅ Status interval started for first message`);
|
|
242
225
|
}
|
|
243
|
-
else {
|
|
244
|
-
log(`[BOT-DISPATCHER] ⏭️ Skipped status interval for steer follower`);
|
|
245
|
-
}
|
|
246
|
-
log(`xy: dispatching to agent (session=${parsed.sessionId})`);
|
|
247
|
-
// Dispatch to OpenClaw core using correct API (following feishu pattern)
|
|
248
|
-
log(`[BOT] 🚀 Starting dispatcher with session: ${route.sessionKey}`);
|
|
249
226
|
// Build session context for AsyncLocalStorage
|
|
250
227
|
const sessionContext = {
|
|
251
228
|
config,
|
|
@@ -259,7 +236,6 @@ export async function handleXYMessage(params) {
|
|
|
259
236
|
onSettled: () => {
|
|
260
237
|
log(`[BOT] 🏁 onSettled called for session: ${route.sessionKey}`);
|
|
261
238
|
log(`[BOT] - isSecondMessage: ${isSecondMessage}`);
|
|
262
|
-
markDispatchIdle();
|
|
263
239
|
// 🔑 减少引用计数
|
|
264
240
|
decrementTaskIdRef(parsed.sessionId);
|
|
265
241
|
// 🔑 如果是第一条消息完成,解锁
|
package/dist/src/client.js
CHANGED
|
@@ -72,14 +72,9 @@ export function getCachedManagerCount() {
|
|
|
72
72
|
* Helps identify connection issues and orphan connections.
|
|
73
73
|
*/
|
|
74
74
|
export function diagnoseAllManagers() {
|
|
75
|
-
console.log("========================================");
|
|
76
|
-
console.log("📊 WebSocket Manager Global Diagnostics");
|
|
77
|
-
console.log("========================================");
|
|
78
75
|
console.log(`Total cached managers: ${wsManagerCache.size}`);
|
|
79
|
-
console.log("");
|
|
80
76
|
if (wsManagerCache.size === 0) {
|
|
81
77
|
console.log("ℹ️ No managers in cache");
|
|
82
|
-
console.log("========================================");
|
|
83
78
|
return;
|
|
84
79
|
}
|
|
85
80
|
let orphanCount = 0;
|
|
@@ -108,7 +103,6 @@ export function diagnoseAllManagers() {
|
|
|
108
103
|
else {
|
|
109
104
|
console.log(`✅ No orphan connections found`);
|
|
110
105
|
}
|
|
111
|
-
console.log("========================================");
|
|
112
106
|
}
|
|
113
107
|
/**
|
|
114
108
|
* Clean up orphan connections across all managers.
|
|
@@ -48,27 +48,9 @@ export async function callCsplApi(questionText, cfg) {
|
|
|
48
48
|
textSource: config.textSource,
|
|
49
49
|
action: config.action,
|
|
50
50
|
};
|
|
51
|
-
// 打印请求信息
|
|
52
|
-
console.log(`[CSPL API] ==================== 发起请求 ====================`);
|
|
53
|
-
console.log(`[CSPL API] URL: ${config.api.url}`);
|
|
54
|
-
console.log(`[CSPL API] Method: POST`);
|
|
55
|
-
console.log(`[CSPL API] Headers:`);
|
|
56
|
-
console.log(`[CSPL API] - x-hag-trace-id: ${headers["x-hag-trace-id"]}`);
|
|
57
|
-
console.log(`[CSPL API] - x-uid: ${headers["x-uid"]}`);
|
|
58
|
-
console.log(`[CSPL API] - x-api-key: ${headers["x-api-key"] ? "***" + headers["x-api-key"].slice(-8) : "undefined"}`);
|
|
59
|
-
console.log(`[CSPL API] - x-request-from: ${headers["x-request-from"]}`);
|
|
60
|
-
console.log(`[CSPL API] - x-skill-id: ${headers["x-skill-id"]}`);
|
|
61
|
-
console.log(`[CSPL API] - content-type: ${headers["content-type"]}`);
|
|
62
|
-
console.log(`[CSPL API] Body:`);
|
|
63
|
-
console.log(`[CSPL API] - questionText: ${questionText.substring(0, 100)}${questionText.length > 100 ? "..." : ""}`);
|
|
64
|
-
console.log(`[CSPL API] - textSource: ${payload.textSource}`);
|
|
65
|
-
console.log(`[CSPL API] - action: ${payload.action}`);
|
|
66
|
-
console.log(`[CSPL API] =================================================`);
|
|
67
51
|
return new Promise((resolve, reject) => {
|
|
68
52
|
const options = buildRequestOptions(config.api.url, headers, config.api.timeout);
|
|
69
53
|
const req = https.request(options, (res) => {
|
|
70
|
-
console.log(`[CSPL API] Response Status: ${res.statusCode}`);
|
|
71
|
-
console.log(`[CSPL API] Response Headers: ${JSON.stringify(res.headers)}`);
|
|
72
54
|
if (res.statusCode && res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
|
|
73
55
|
reject(new Error(`[CSPL] HTTP error: ${res.statusCode}`));
|
|
74
56
|
return;
|
|
@@ -81,13 +63,10 @@ export async function callCsplApi(questionText, cfg) {
|
|
|
81
63
|
try {
|
|
82
64
|
const result = parseResponse(data);
|
|
83
65
|
console.log(`[CSPL API] ✅ 请求成功`);
|
|
84
|
-
console.log(`[CSPL API] Response Body: ${data.substring(0, 200)}${data.length > 200 ? "..." : ""}`);
|
|
85
|
-
console.log(`[CSPL API] =================================================`);
|
|
86
66
|
resolve(result);
|
|
87
67
|
}
|
|
88
68
|
catch (e) {
|
|
89
69
|
console.error(`[CSPL API] ❌ 请求失败: ${e instanceof Error ? e.message : String(e)}`);
|
|
90
|
-
console.error(`[CSPL API] Response Body: ${data}`);
|
|
91
70
|
reject(e);
|
|
92
71
|
}
|
|
93
72
|
});
|
package/dist/src/file-upload.js
CHANGED
|
@@ -55,12 +55,10 @@ export class XYFileUploadService {
|
|
|
55
55
|
throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
|
|
56
56
|
}
|
|
57
57
|
const prepareData = await prepareResp.json();
|
|
58
|
-
console.log(`[XY File Upload] Prepare response:`, JSON.stringify(prepareData, null, 2));
|
|
59
58
|
if (prepareData.code !== "0") {
|
|
60
59
|
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
61
60
|
}
|
|
62
61
|
const { objectId, draftId, uploadInfos } = prepareData;
|
|
63
|
-
console.log(`[XY File Upload] Prepare complete: objectId=${objectId}, draftId=${draftId}`);
|
|
64
62
|
// Phase 2: Upload
|
|
65
63
|
console.log(`[XY File Upload] Phase 2: Upload file data`);
|
|
66
64
|
const uploadInfo = uploadInfos[0]; // Single-part upload
|
|
@@ -69,11 +67,8 @@ export class XYFileUploadService {
|
|
|
69
67
|
headers: uploadInfo.headers,
|
|
70
68
|
body: fileBuffer,
|
|
71
69
|
});
|
|
72
|
-
console.log(`[XY File Upload] Upload response status: ${uploadResp.status}, url: ${uploadInfo.url}`);
|
|
73
|
-
console.log(`[XY File Upload] Upload response headers:`, JSON.stringify(Object.fromEntries(uploadResp.headers.entries()), null, 2));
|
|
74
70
|
if (!uploadResp.ok) {
|
|
75
71
|
const uploadErrorText = await uploadResp.text();
|
|
76
|
-
console.log(`[XY File Upload] Upload error response:`, uploadErrorText);
|
|
77
72
|
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
78
73
|
}
|
|
79
74
|
console.log(`[XY File Upload] Upload complete`);
|
|
@@ -96,7 +91,6 @@ export class XYFileUploadService {
|
|
|
96
91
|
throw new Error(`Complete failed: HTTP ${completeResp.status}`);
|
|
97
92
|
}
|
|
98
93
|
const completeData = await completeResp.json();
|
|
99
|
-
console.log(`[XY File Upload] Complete response:`, JSON.stringify(completeData, null, 2));
|
|
100
94
|
console.log(`[XY File Upload] File upload successful: ${fileName} → objectId=${objectId}`);
|
|
101
95
|
return objectId;
|
|
102
96
|
}
|
|
@@ -110,7 +104,6 @@ export class XYFileUploadService {
|
|
|
110
104
|
* Uses completeAndQuery endpoint to get the file URL directly.
|
|
111
105
|
*/
|
|
112
106
|
async uploadFileAndGetUrl(filePath, objectType = "TEMPORARY_MATERIAL_DOC") {
|
|
113
|
-
console.log(`[XY File Upload] Starting file upload with URL retrieval: ${filePath}`);
|
|
114
107
|
try {
|
|
115
108
|
// Read file
|
|
116
109
|
const fileBuffer = await fs.readFile(filePath);
|
|
@@ -143,7 +136,6 @@ export class XYFileUploadService {
|
|
|
143
136
|
throw new Error(`Prepare failed: HTTP ${prepareResp.status}`);
|
|
144
137
|
}
|
|
145
138
|
const prepareData = await prepareResp.json();
|
|
146
|
-
console.log(`[XY File Upload] Prepare response:`, JSON.stringify(prepareData, null, 2));
|
|
147
139
|
if (prepareData.code !== "0") {
|
|
148
140
|
throw new Error(`Prepare failed: ${prepareData.desc}`);
|
|
149
141
|
}
|
|
@@ -160,7 +152,6 @@ export class XYFileUploadService {
|
|
|
160
152
|
console.log(`[XY File Upload] Upload response status: ${uploadResp.status}`);
|
|
161
153
|
if (!uploadResp.ok) {
|
|
162
154
|
const uploadErrorText = await uploadResp.text();
|
|
163
|
-
console.log(`[XY File Upload] Upload error response:`, uploadErrorText);
|
|
164
155
|
throw new Error(`Upload failed: HTTP ${uploadResp.status}`);
|
|
165
156
|
}
|
|
166
157
|
console.log(`[XY File Upload] Upload complete`);
|
|
@@ -183,13 +174,12 @@ export class XYFileUploadService {
|
|
|
183
174
|
throw new Error(`CompleteAndQuery failed: HTTP ${completeResp.status}`);
|
|
184
175
|
}
|
|
185
176
|
const completeData = await completeResp.json();
|
|
186
|
-
console.log(`[XY File Upload] CompleteAndQuery response:`, JSON.stringify(completeData, null, 2));
|
|
187
177
|
// Extract file URL from response
|
|
188
178
|
const fileUrl = completeData?.fileDetailInfo?.url || "";
|
|
189
179
|
if (!fileUrl) {
|
|
190
180
|
throw new Error("No file URL returned from completeAndQuery");
|
|
191
181
|
}
|
|
192
|
-
console.log(`[XY File Upload] File upload successful
|
|
182
|
+
console.log(`[XY File Upload] File upload successful`);
|
|
193
183
|
return fileUrl;
|
|
194
184
|
}
|
|
195
185
|
catch (error) {
|
package/dist/src/formatter.js
CHANGED
|
@@ -55,7 +55,7 @@ export async function sendA2AResponse(params) {
|
|
|
55
55
|
log(`[A2A_RESPONSE] 📤 Sending A2A artifact-update response: taskId: ${taskId}`);
|
|
56
56
|
log(`[A2A_RESPONSE] - append: ${append}`);
|
|
57
57
|
log(`[A2A_RESPONSE] - final: ${final}`);
|
|
58
|
-
log(`[A2A_RESPONSE] - text: ${text}`);
|
|
58
|
+
log(`[A2A_RESPONSE] - text: ${text.length <= 10 ? text : text.slice(0, 5) + '***' + text.slice(-5)}`);
|
|
59
59
|
log(`[A2A_RESPONSE] - files count: ${files?.length ?? 0}`);
|
|
60
60
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
61
61
|
log(`[A2A_RESPONSE] ✅ Message sent successfully`);
|
|
@@ -146,11 +146,8 @@ export async function sendStatusUpdate(params) {
|
|
|
146
146
|
// 📋 Log complete response body
|
|
147
147
|
log(`[A2A_STATUS] 📤 Sending A2A status-update:`);
|
|
148
148
|
log(`[A2A_STATUS] - taskId: ${taskId}`);
|
|
149
|
-
log(`[A2A_STATUS] - messageId: ${messageId}`);
|
|
150
|
-
log(`[A2A_STATUS] - state: ${state}`);
|
|
151
149
|
log(`[A2A_STATUS] - text: "${text}"`);
|
|
152
150
|
await wsManager.sendMessage(sessionId, outboundMessage);
|
|
153
|
-
log(`[A2A_STATUS] ✅ Status update sent successfully`);
|
|
154
151
|
}
|
|
155
152
|
/**
|
|
156
153
|
* Send a command as an artifact update (final=false).
|
package/dist/src/monitor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { handleXYMessage } from "./bot.js";
|
|
|
4
4
|
import { parseA2AMessage } from "./parser.js";
|
|
5
5
|
import { hasActiveTask } from "./task-manager.js";
|
|
6
6
|
import { handleTriggerEvent } from "./trigger-handler.js";
|
|
7
|
+
import { cleanupStaleTempFiles } from "./reply-dispatcher.js";
|
|
7
8
|
/**
|
|
8
9
|
* Per-session serial queue that ensures messages from the same session are processed
|
|
9
10
|
* in arrival order while allowing different sessions to run concurrently.
|
|
@@ -67,7 +68,7 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
67
68
|
// Event handlers (defined early so they can be referenced in cleanup)
|
|
68
69
|
const messageHandler = (message, sessionId, serverId) => {
|
|
69
70
|
const messageKey = `${sessionId}::${message.id}`;
|
|
70
|
-
log(`[MONITOR-HANDLER] ####### messageHandler triggered:
|
|
71
|
+
log(`[MONITOR-HANDLER] ####### messageHandler triggered: sessionId=${sessionId}, messageId=${message.id} #######`);
|
|
71
72
|
// ✅ Report health: received a message
|
|
72
73
|
trackEvent?.();
|
|
73
74
|
// Check for duplicate message handling
|
|
@@ -75,17 +76,14 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
75
76
|
error(`[MONITOR-HANDLER] ⚠️ WARNING: Duplicate message detected! messageKey=${messageKey}, this may cause duplicate dispatchers!`);
|
|
76
77
|
}
|
|
77
78
|
activeMessages.add(messageKey);
|
|
78
|
-
log(`[MONITOR-HANDLER] 📝 Active messages count: ${activeMessages.size}, messageKey: ${messageKey}`);
|
|
79
79
|
const task = async () => {
|
|
80
80
|
try {
|
|
81
|
-
log(`[MONITOR-HANDLER] 🚀 Starting handleXYMessage for messageKey=${messageKey}`);
|
|
82
81
|
await handleXYMessage({
|
|
83
82
|
cfg,
|
|
84
83
|
runtime,
|
|
85
84
|
message,
|
|
86
85
|
accountId, // ✅ Pass accountId ("default")
|
|
87
86
|
});
|
|
88
|
-
log(`[MONITOR-HANDLER] ✅ Completed handleXYMessage for messageKey=${messageKey}`);
|
|
89
87
|
}
|
|
90
88
|
catch (err) {
|
|
91
89
|
// ✅ Only log error, don't re-throw to prevent gateway restart
|
|
@@ -94,7 +92,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
94
92
|
finally {
|
|
95
93
|
// Remove from active messages when done
|
|
96
94
|
activeMessages.delete(messageKey);
|
|
97
|
-
log(`[MONITOR-HANDLER] 🧹 Cleaned up messageKey=${messageKey}, remaining active: ${activeMessages.size}`);
|
|
98
95
|
}
|
|
99
96
|
};
|
|
100
97
|
// 🔑 核心改造:检测steer模式
|
|
@@ -107,7 +104,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
107
104
|
// Steer模式且有活跃任务:不入队列,直接并发执行
|
|
108
105
|
log(`[MONITOR-HANDLER] 🔄 STEER MODE: Executing concurrently for messageKey=${messageKey}`);
|
|
109
106
|
log(`[MONITOR-HANDLER] - sessionId: ${parsed.sessionId}`);
|
|
110
|
-
log(`[MONITOR-HANDLER] - Bypassing queue to allow message insertion`);
|
|
111
107
|
void task().catch((err) => {
|
|
112
108
|
error(`XY gateway: concurrent steer task failed for ${messageKey}: ${String(err)}`);
|
|
113
109
|
activeMessages.delete(messageKey);
|
|
@@ -115,7 +111,6 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
115
111
|
}
|
|
116
112
|
else {
|
|
117
113
|
// 正常模式:入队列串行执行
|
|
118
|
-
log(`[MONITOR-HANDLER] 📋 NORMAL MODE: Enqueuing for messageKey=${messageKey}`);
|
|
119
114
|
void enqueue(sessionId, task).catch((err) => {
|
|
120
115
|
error(`XY gateway: queue processing failed for session ${sessionId}: ${String(err)}`);
|
|
121
116
|
activeMessages.delete(messageKey);
|
|
@@ -207,8 +202,8 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
207
202
|
wsManager.on("disconnected", disconnectedHandler);
|
|
208
203
|
wsManager.on("error", errorHandler);
|
|
209
204
|
wsManager.on("trigger-event", triggerEventHandler);
|
|
210
|
-
// Start periodic health check (every
|
|
211
|
-
console.log("🏥 Starting periodic health check (every
|
|
205
|
+
// Start periodic health check (every 6 hours)
|
|
206
|
+
console.log("🏥 Starting periodic health check (every 6 hours)...");
|
|
212
207
|
healthCheckInterval = setInterval(() => {
|
|
213
208
|
console.log("🏥 [HEALTH CHECK] Periodic WebSocket diagnostics...");
|
|
214
209
|
diagnoseAllManagers();
|
|
@@ -217,7 +212,9 @@ export async function monitorXYProvider(opts = {}) {
|
|
|
217
212
|
if (cleaned > 0) {
|
|
218
213
|
console.log(`🧹 [HEALTH CHECK] Auto-cleaned ${cleaned} manager(s) with orphan connections`);
|
|
219
214
|
}
|
|
220
|
-
|
|
215
|
+
// Cleanup stale temp files (older than 24 hours)
|
|
216
|
+
void cleanupStaleTempFiles();
|
|
217
|
+
}, 6 * 60 * 60 * 1000); // 6 hours
|
|
221
218
|
// Connect to WebSocket servers
|
|
222
219
|
wsManager.connect()
|
|
223
220
|
.then(() => {
|
package/dist/src/outbound.js
CHANGED
|
@@ -90,13 +90,6 @@ export const xyOutbound = {
|
|
|
90
90
|
};
|
|
91
91
|
},
|
|
92
92
|
sendText: async ({ cfg, to, text, accountId }) => {
|
|
93
|
-
// Log parameters
|
|
94
|
-
console.log(`[xyOutbound.sendText] Called with:`, {
|
|
95
|
-
to,
|
|
96
|
-
accountId,
|
|
97
|
-
textLength: text?.length || 0,
|
|
98
|
-
textPreview: text?.slice(0, 100),
|
|
99
|
-
});
|
|
100
93
|
// Resolve configuration
|
|
101
94
|
const config = resolveXYConfig(cfg);
|
|
102
95
|
// Handle default push marker (for cron jobs without explicit target)
|
|
@@ -112,7 +105,7 @@ export const xyOutbound = {
|
|
|
112
105
|
let pushDataId;
|
|
113
106
|
try {
|
|
114
107
|
pushDataId = await savePushData(text);
|
|
115
|
-
console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId}`);
|
|
108
|
+
console.log(`[xyOutbound.sendText] ✅ Push data saved with ID: ${pushDataId.substring(0, 20)}`);
|
|
116
109
|
}
|
|
117
110
|
catch (error) {
|
|
118
111
|
console.error(`[xyOutbound.sendText] ❌ Failed to save push data:`, error);
|
|
@@ -146,7 +139,6 @@ export const xyOutbound = {
|
|
|
146
139
|
let failureCount = 0;
|
|
147
140
|
for (const pushId of pushIdList) {
|
|
148
141
|
try {
|
|
149
|
-
console.log(`[xyOutbound.sendText] Sending to pushId: ${pushId.substring(0, 20)}...`);
|
|
150
142
|
// 传入 pushId 和 pushDataId,使用 kind="data" 格式
|
|
151
143
|
await pushService.sendPush(pushText, title, undefined, actualTo, pushDataId, pushId);
|
|
152
144
|
successCount++;
|
|
@@ -158,8 +150,6 @@ export const xyOutbound = {
|
|
|
158
150
|
// 单个 pushId 发送失败不影响其他,继续处理下一个
|
|
159
151
|
}
|
|
160
152
|
}
|
|
161
|
-
console.log(`[xyOutbound.sendText] 📊 Broadcast summary: ${successCount} success, ${failureCount} failures`);
|
|
162
|
-
console.log(`[xyOutbound.sendText] Completed successfully`);
|
|
163
153
|
// Return message info
|
|
164
154
|
return {
|
|
165
155
|
channel: "xiaoyi-channel",
|
|
@@ -168,14 +158,6 @@ export const xyOutbound = {
|
|
|
168
158
|
};
|
|
169
159
|
},
|
|
170
160
|
sendMedia: async ({ cfg, to, text, mediaUrl, accountId, mediaLocalRoots }) => {
|
|
171
|
-
// Log parameters
|
|
172
|
-
console.log(`[xyOutbound.sendMedia] Called with:`, {
|
|
173
|
-
to,
|
|
174
|
-
accountId,
|
|
175
|
-
text,
|
|
176
|
-
mediaUrl,
|
|
177
|
-
mediaLocalRoots,
|
|
178
|
-
});
|
|
179
161
|
// Parse to: "sessionId::taskId"
|
|
180
162
|
const parts = to.split("::");
|
|
181
163
|
if (parts.length !== 2) {
|
package/dist/src/push.js
CHANGED
|
@@ -34,15 +34,7 @@ export class XYPushService {
|
|
|
34
34
|
// Use provided pushId or fall back to config pushId
|
|
35
35
|
const actualPushId = pushId || this.config.pushId;
|
|
36
36
|
console.log(`[PUSH] 📤 Preparing to send push message`);
|
|
37
|
-
console.log(`[PUSH] - Title: "${title}"`);
|
|
38
|
-
console.log(`[PUSH] - Content length: ${content.length} chars`);
|
|
39
|
-
console.log(`[PUSH] - Session ID: ${sessionId || 'none'}`);
|
|
40
|
-
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
41
|
-
console.log(`[PUSH] - Push URL: ${pushUrl}`);
|
|
42
37
|
console.log(`[PUSH] - Using pushId: ${actualPushId.substring(0, 20)}...`);
|
|
43
|
-
console.log(`[PUSH] - Full pushId: ${actualPushId}`);
|
|
44
|
-
console.log(`[PUSH] - API ID: ${this.config.apiId}`);
|
|
45
|
-
console.log(`[PUSH] - UID: ${this.config.uid}`);
|
|
46
38
|
try {
|
|
47
39
|
const requestBody = {
|
|
48
40
|
jsonrpc: "2.0",
|
|
@@ -75,7 +67,6 @@ export class XYPushService {
|
|
|
75
67
|
],
|
|
76
68
|
},
|
|
77
69
|
};
|
|
78
|
-
console.log(`[PUSH] Full request body:`, JSON.stringify(requestBody, null, 2));
|
|
79
70
|
const response = await fetch(pushUrl, {
|
|
80
71
|
method: "POST",
|
|
81
72
|
headers: {
|
|
@@ -91,21 +82,16 @@ export class XYPushService {
|
|
|
91
82
|
// Log response status and headers
|
|
92
83
|
console.log(`[PUSH] 📥 Response received`);
|
|
93
84
|
console.log(`[PUSH] - HTTP Status: ${response.status} ${response.statusText}`);
|
|
94
|
-
console.log(`[PUSH] - Content-Type: ${response.headers.get('content-type')}`);
|
|
95
|
-
console.log(`[PUSH] - Content-Length: ${response.headers.get('content-length')}`);
|
|
96
85
|
if (!response.ok) {
|
|
97
86
|
const errorText = await response.text();
|
|
98
87
|
console.log(`[PUSH] ❌ Push request failed`);
|
|
99
88
|
console.log(`[PUSH] - HTTP Status: ${response.status}`);
|
|
100
|
-
console.log(`[PUSH] - Response body: ${errorText}`);
|
|
101
89
|
throw new Error(`Push failed: HTTP ${response.status} - ${errorText}`);
|
|
102
90
|
}
|
|
103
91
|
// Try to parse JSON response with detailed error handling
|
|
104
92
|
let result;
|
|
105
93
|
try {
|
|
106
94
|
const responseText = await response.text();
|
|
107
|
-
console.log(`[PUSH] 📄 Response body length: ${responseText.length} chars`);
|
|
108
|
-
console.log(`[PUSH] 📄 Response body preview: ${responseText.substring(0, 200)}`);
|
|
109
95
|
if (!responseText || responseText.trim() === '') {
|
|
110
96
|
console.log(`[PUSH] ⚠️ Received empty response body`);
|
|
111
97
|
result = {};
|
|
@@ -120,20 +106,13 @@ export class XYPushService {
|
|
|
120
106
|
throw new Error(`Invalid JSON response from push service: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
121
107
|
}
|
|
122
108
|
console.log(`[PUSH] ✅ Push message sent successfully`);
|
|
123
|
-
console.log(`[PUSH] - Title: "${title}"`);
|
|
124
109
|
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
125
|
-
console.log(`[PUSH] - Used pushId: ${actualPushId.substring(0, 20)}...`);
|
|
126
|
-
console.log(`[PUSH] - Response:`, result);
|
|
127
110
|
}
|
|
128
111
|
catch (error) {
|
|
129
112
|
console.log(`[PUSH] ❌ Failed to send push message`);
|
|
130
|
-
console.log(`[PUSH] - Trace ID: ${traceId}`);
|
|
131
|
-
console.log(`[PUSH] - Target URL: ${pushUrl}`);
|
|
132
|
-
console.log(`[PUSH] - Push ID: ${actualPushId.substring(0, 20)}...`);
|
|
133
113
|
if (error instanceof Error) {
|
|
134
114
|
console.log(`[PUSH] - Error name: ${error.name}`);
|
|
135
115
|
console.log(`[PUSH] - Error message: ${error.message}`);
|
|
136
|
-
console.log(`[PUSH] - Error stack:`, error.stack);
|
|
137
116
|
}
|
|
138
117
|
else {
|
|
139
118
|
console.log(`[PUSH] - Error:`, error);
|
|
@@ -8,6 +8,10 @@ export interface CreateXYReplyDispatcherParams {
|
|
|
8
8
|
accountId: string;
|
|
9
9
|
isSteerFollower?: boolean;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
13
|
+
*/
|
|
14
|
+
export declare function cleanupStaleTempFiles(tempDir?: string): Promise<void>;
|
|
11
15
|
/**
|
|
12
16
|
* Create a reply dispatcher for XY channel messages.
|
|
13
17
|
* Follows feishu pattern with status updates and streaming support.
|
|
@@ -4,29 +4,34 @@ import { resolveXYConfig } from "./config.js";
|
|
|
4
4
|
import { getCurrentTaskId, getCurrentMessageId } from "./task-manager.js";
|
|
5
5
|
import fs from "fs/promises";
|
|
6
6
|
import path from "path";
|
|
7
|
+
const TEMP_FILE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
7
8
|
/**
|
|
8
|
-
* 清理 /tmp/xy_channel
|
|
9
|
+
* 清理 /tmp/xy_channel 目录中超过 24 小时的旧文件
|
|
9
10
|
*/
|
|
10
|
-
async function
|
|
11
|
+
export async function cleanupStaleTempFiles(tempDir = "/tmp/xy_channel") {
|
|
11
12
|
try {
|
|
12
13
|
const stats = await fs.stat(tempDir).catch(() => null);
|
|
13
14
|
if (!stats?.isDirectory()) {
|
|
14
|
-
return;
|
|
15
|
+
return;
|
|
15
16
|
}
|
|
16
17
|
const files = await fs.readdir(tempDir);
|
|
18
|
+
const now = Date.now();
|
|
17
19
|
let cleanedCount = 0;
|
|
18
20
|
for (const file of files) {
|
|
19
21
|
const filePath = path.join(tempDir, file);
|
|
20
22
|
try {
|
|
21
|
-
await fs.
|
|
22
|
-
|
|
23
|
+
const fileStat = await fs.stat(filePath);
|
|
24
|
+
if (now - fileStat.mtimeMs > TEMP_FILE_TTL_MS) {
|
|
25
|
+
await fs.unlink(filePath);
|
|
26
|
+
cleanedCount++;
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
catch (err) {
|
|
25
|
-
//
|
|
30
|
+
// 忽略单个文件处理失败
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
33
|
if (cleanedCount > 0) {
|
|
29
|
-
console.log(`[CLEANUP] 🧹 Cleaned ${cleanedCount} files from ${tempDir}`);
|
|
34
|
+
console.log(`[CLEANUP] 🧹 Cleaned ${cleanedCount} stale files (>${TEMP_FILE_TTL_MS / 1000 / 3600}h) from ${tempDir}`);
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
catch (err) {
|
|
@@ -43,9 +48,7 @@ export function createXYReplyDispatcher(params) {
|
|
|
43
48
|
const log = runtime?.log ?? console.log;
|
|
44
49
|
const error = runtime?.error ?? console.error;
|
|
45
50
|
log(`[DISPATCHER-CREATE] ******* Creating dispatcher *******`);
|
|
46
|
-
log(`[DISPATCHER-CREATE] - sessionId: ${sessionId}`);
|
|
47
51
|
log(`[DISPATCHER-CREATE] - taskId: ${taskId}`);
|
|
48
|
-
log(`[DISPATCHER-CREATE] - messageId: ${messageId}`);
|
|
49
52
|
log(`[DISPATCHER-CREATE] - isSteerFollower: ${isSteerFollower ?? false}`);
|
|
50
53
|
// 初始taskId和messageId(作为fallback)
|
|
51
54
|
const initialTaskId = taskId;
|
|
@@ -241,7 +244,6 @@ export function createXYReplyDispatcher(params) {
|
|
|
241
244
|
}
|
|
242
245
|
}
|
|
243
246
|
stopStatusInterval();
|
|
244
|
-
void cleanupTempDir();
|
|
245
247
|
},
|
|
246
248
|
onCleanup: () => {
|
|
247
249
|
const currentTaskId = getActiveTaskId();
|
package/dist/src/websocket.js
CHANGED
|
@@ -318,30 +318,37 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
318
318
|
* Handle incoming message from server.
|
|
319
319
|
*/
|
|
320
320
|
handleMessage(data) {
|
|
321
|
-
console.log("[WEBSOCKET-HANDLE] >>>>>>> Receiving message... <<<<<<<");
|
|
322
321
|
try {
|
|
323
322
|
const messageStr = data.toString();
|
|
324
|
-
console.log(`[WS-RECV] Raw message frame, size: ${messageStr.length}
|
|
323
|
+
console.log(`[WS-RECV] Raw message frame, size: ${messageStr.length} characters`);
|
|
325
324
|
const parsed = JSON.parse(messageStr);
|
|
326
325
|
// 提取并打印消息内容(只显示 text,data 只打印提示)
|
|
327
326
|
const parts = parsed.params?.message?.parts;
|
|
328
327
|
if (parts && Array.isArray(parts) && parts.length > 0) {
|
|
329
328
|
const textParts = parts.filter((p) => p?.kind === "text");
|
|
330
329
|
const dataParts = parts.filter((p) => p?.kind === "data");
|
|
331
|
-
// 打印 text
|
|
330
|
+
// 打印 text 内容(隐藏敏感信息)
|
|
332
331
|
if (textParts.length > 0) {
|
|
333
332
|
const textContents = textParts
|
|
334
333
|
.map((p) => p?.text || "")
|
|
335
334
|
.filter((text) => text.length > 0)
|
|
336
335
|
.join(" ");
|
|
337
336
|
if (textContents.length > 0) {
|
|
338
|
-
|
|
337
|
+
// 隐藏中间内容,只保留前后各5个字符
|
|
338
|
+
let maskedText;
|
|
339
|
+
if (textContents.length <= 8) {
|
|
340
|
+
// 如果长度 <= 8,显示前2个 + *** + 后2个
|
|
341
|
+
maskedText = textContents.length >= 4
|
|
342
|
+
? `${textContents.slice(0, 2)}***${textContents.slice(-2)}`
|
|
343
|
+
: `${textContents.slice(0, 1)}***${textContents.slice(-1)}`;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
// 如果长度 > 8,显示前5个 + *** + 后5个
|
|
347
|
+
maskedText = `${textContents.slice(0, 5)}***${textContents.slice(-5)}`;
|
|
348
|
+
}
|
|
349
|
+
console.log("[WS-RECV] Text:", maskedText);
|
|
339
350
|
}
|
|
340
351
|
}
|
|
341
|
-
// 打印 data 提示
|
|
342
|
-
if (dataParts.length > 0) {
|
|
343
|
-
console.log("[WS-RECV] Data: received data message(s)");
|
|
344
|
-
}
|
|
345
352
|
}
|
|
346
353
|
// Check if message is in direct A2A JSON-RPC format (server push)
|
|
347
354
|
if (parsed.jsonrpc === "2.0") {
|
|
@@ -392,7 +399,6 @@ export class XYWebSocketManager extends EventEmitter {
|
|
|
392
399
|
return;
|
|
393
400
|
}
|
|
394
401
|
// Emit message event for non-data-only messages
|
|
395
|
-
console.log("[XY] *** EMITTING message event (Direct A2A path) ***");
|
|
396
402
|
this.emit("message", a2aRequest, sessionId);
|
|
397
403
|
return;
|
|
398
404
|
}
|