@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 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
  // 🔑 如果是第一条消息完成,解锁
@@ -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
  });
@@ -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: ${fileName} → URL=${fileUrl}`);
182
+ console.log(`[XY File Upload] File upload successful`);
193
183
  return fileUrl;
194
184
  }
195
185
  catch (error) {
@@ -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).
@@ -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: serverId=${serverId}, sessionId=${sessionId}, messageId=${message.id} #######`);
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 5 minutes)
211
- console.log("🏥 Starting periodic health check (every 5 minutes)...");
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
- }, 5 * 60 * 1000); // 5 minutes
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(() => {
@@ -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 cleanupTempDir(tempDir = "/tmp/xy_channel") {
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.unlink(filePath);
22
- cleanedCount++;
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();
@@ -244,6 +244,7 @@ b. 操作超时时间为2分钟(120秒),请勿重复调用此工具,如
244
244
  };
245
245
  // Send WebSocket message
246
246
  await wsManager.sendMessage(sessionId, agentResponse);
247
+ console.log(`send ${fileName} file to user success`);
247
248
  sentFiles.push({ fileName, fileId });
248
249
  }
249
250
  return {
@@ -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} bytes`);
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
- console.log("[WS-RECV] Text:", textContents);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ynhcj/xiaoyi-channel",
3
- "version": "0.0.53-beta",
3
+ "version": "0.0.55-beta",
4
4
  "description": "OpenClaw Xiaoyi Channel plugin - Xiaoyi A2A protocol integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",